/**main.js
 * @author dvd
 * 
 * all the js for oothanks.com pages
 * 
 * To do:
 * separate index-only javascript, so that the initial load is not too much
 */

// onReady is here! #######################################################################
Ext.onReady(function() {
	Ext.BLANK_IMAGE_URL = "/lib/ext2/resources/images/default/s.gif";
    Ext.QuickTips.init();
	// make iconImage refs from icons (initialised in fields.js)
	for (var iconName in icons) {
		iconImage[iconName] = "<img src='" + icons[iconName] + "' alt='" + iconName + " icon'>";
	}	
//	Ext.state.Manager.setProvider(new Ext.state.CookieProvider());

	// make the main page a viewport, with a heading

	oot.viewport = new Ext.Viewport({
		layout:'fit',
		items: new Ext.Panel({	// this internal panel enables us to have padding insider the viewport
			style:'padding:15px; background:#fdd;',
			layout: 'border',
			items: [{
				region: 'north',
				html: "<h1 class='title'>&nbsp;" + SITETITLE + "</h1><div id='topmenu2'></div>",
				height:64,
				border: false
			}, {
				region: 'center',
				autoheight: true,
				border: true,
				layout: 'card',
				activeItem: 0,
				id:'card-holder',
				border: false,
				items: [
					new Ext.Panel({
						id: 'ootPage-home',
						layout: 'border',
						autoScroll: true,
						items: [
/*							{
								region: 'center',
								id: 'oot-home-panel-c',
								layout: 'fit',
								bodyStyle:'padding:5px',
								split:true,
								collapsible: false,
								titlebar: false,
								autoScroll: true,
								autoLoad: '/content/home.php'
							},
*/
							{
								region: 'center',
								id: 'oot-home-panel-c',
								layout: 'fit',
								bodyStyle:'padding:5px',
								split:true,
								collapsible: false,
								titlebar: false,
//								autoScroll: true,
//								autoLoad: '/content/home.php'
								items: new Ext.Panel({
									layout:'border',
									border: false,
									defaults:{border:false},
									items: [
										{	region:'west',
											id: 'oot-home-panel-c-w',
											width:'50%',
											autoLoad: '/content/home_leftpanel.php',
											autoScroll: true,
											bodyStyle:'padding-right:5px;'
										},
										{	region:'center',
											autoLoad: '/content/home.php',
											autoScroll: true,
											bodyStyle:'padding-left:5px;'							
										}
									]
								})
							},
							{
								region: 'east',
								id: 'oot-home-panel-e',
								title:'What would you like to do?',
								width: 270,
								titlebar: true,
								autoScroll: true
							}
						]
					}),
					{id: 'ootPage-newaccount', title:'Create a New Account', layout:'fit', bodyStyle:'padding:5px', autoLoad:'/content/newaccount.php',autoScroll: true},
					{id: 'ootPage-myaccount', title:'My Account Information', layout:'fit'},
					{id: 'ootPage-listsiown', title:'My Lists', layout:'fit'},
					{id: 'ootPage-listsimanage', title:'Lists I Manage', layout:'fit'},
					{id: 'ootPage-mychildrenslists', title:"My Children's Lists", layout:'fit'},
					{id: 'ootPage-myfriendslists', title:"My Friends' Lists", layout:'fit'}
				]
			},{
				region: 'south',
				html: "<div id='oot-south' style='text-align:center'><iframe id='oot-southIframe' src='/listbycode/0' height='15' frameborder='0' width='468' scrolling='no' allowtransparency='true' hspace='0' vspace='0' marginheight='0' marginwidth='0'></iframe></div>",
				height:20,
				border: false
			}]
		})
	});
	// do an onload for the home_leftpanel panel, to make the images clickable
	Ext.getCmp('oot-home-panel-c-w').getUpdater().on('update', function(){
		var fn = function(){
			if ($$('img.hcpLeftImage').length == 0) {
				setTimeout(fn, 200);
				return false;
			}
			oot.imagePopup.addEventToClass('hcpLeftImage');
			oot.imagePopup.addEventToClass('hcpRightImage');	
			return true;
		}
		fn();
	});
	// fill home page with some text
//	oot.getSiteContent('home', 'oot-home-panel-c');
	
	// get some important data
	var topMenu = DHW.getJSON('ootData-topmenu');
	menu.data = topMenu;
//	user.topMenu = topMenu; // to be deprecated
	user.loggedIn = (topMenu.myAccount !== undefined);
	user.userName = (user.loggedIn) ? topMenu.myAccount : false; 
	
	menu.draw();
//	var show = DHW.getJSON('ootData-pageData');
//	menu.go(show);
	menu.goHome();

	// remove the loading mask
	var mask = Ext.get('loading-mask');
	mask.fadeOut({remove:true});

	// deal with any message
	if (message[0] !== undefined) {
		Ext.MessageBox.alert(message[0],message[1]);
	}
	// start the history checking
	menu.history.start();
});

var oot = { // a global object for storing stuff... aka a namespace 

	// properties ###################################################
	dataPort: 'http://' + location.hostname + '/lib/dataport.php',
	page: location.pathname,
	show: {}, // this object describes the page/tab that's being shown
	tabPanel: "",	// will store the object
	tabs: {},	// array of tabs, keyed by id
	currentTab: "",	// used by loadTab to know whether to refresh a tab
	tabActivatedByMenu: false,	// a flag to help loadTab to see what activated the tab
	contentPath: "/includes/content.php",
	helpImagePath: "/images/help2.png",
	height:480,	// the basic height of the content-inner div
	categoryHints: { // info about what the categories mean
		'all': "Because you're the owner, you can only see what's on the list, not what's been bought!",
		'my_pencilled': "These are all the items that you've said you might buy, but you haven't confirmed that you've bought them yet.",
		'my_bought':"These are the items that you have bought from the list.",
		'available': "All these items are available for you to get as a present.",
		'pencilled': "These items have been pencilled-in by other people - i.e. they're thinking of buying them, but they haven't actually bought them yet.",
		'bought': "These items from the list have already been bought."
	},
	weblinkStartURL: 'http://www.google.com',
	pageInfo: {
		home: {
			title:"",
			tabs:{},
			script:""
		},
		newaccount: {
			title:"",
			tabs:{},
			script: 'newaccount'
		},
		myaccount:{
			title:"My Account Information",
			tabs: {
				view: {
					title: 'View', icon:'view',
					config: {
						autoScroll: true,
						bodyStyle: 'padding:5px'
					}
				},
				managechildren: {title:'Manage Children', icon:'children', tabTest: function(){
					return (menu.data.myLists !== undefined && menu.data.myLists.children !== undefined);
				}},
				friendsfamily: {title:'Friends &amp; Family', icon:'friends'},
				items:{title:'Buying / Bought', icon:'buying'},
				todo:{title:'To Do...',icon:'todo'}
			},
			script: 'myaccount'
		},
		listsiown:{
			title:"Lists I Own",
			tabs: {			
				listitems: {title:'The List', icon:'list'},
				listdetails: {title:'Edit Details', icon:'edit', config:{autoScroll: true}},
				listusers: {title:'Friends &amp; Family', config:{autoScroll: true}, icon:'friends'},
				newlist2: {title:'Create New List', icon:'new'}
			},
			script: 'listpage'
		},
		listsimanage:{
			title:"Lists I Manage",
			tabs: {			
				listitems: {title:'The List', icon:'list'},
				listdetails: {title:'Edit Details', icon:'edit', config:{autoScroll: true}},
				listusers: {title:'Friends &amp; Family', icon:'friends', config:{autoScroll: true}}
			},
			script: 'listpage'			
		},
		mychildrenslists:{
			title:"My Children's Lists",
			tabs: {			
				listitems: {title:'The List', icon:'list'},
				listdetails: {title:'Edit Details', icon:'edit', config:{autoScroll: true}},
				listusers: {title:'Friends &amp; Family', icon:'friends'}
//				managechildren: {title:'Manage Children'}
			},
			script: 'listpage'			
		},
		myfriendslists:{
			title:"My Friends' Lists",
			tabs: {			
				listitems: {title:'The List', icon:'list'},
//				listdetails: {title:'Edit Details'},
//				listusers: {title:'Manage Users'},
				findalist: {title:'Find a List', icon:'find'}	//nb the tab has a different name from the menu item
			},
			script: 'listpage'			
		}
	},
	xxtabInfo: {
		myaccount:{
			view:{title:'View'},
			edit:{title:'Edit'},
			items:{title:'Buying / Bought'},
			todo:{title:'To Do...'}
		},
		listsiown:{
			listitems: {title:'The List'},
			listdetails: {title:'Edit Details'},
			listusers: {title:'Manage Users'},
			newlist: {title:'Create New List'}
		},
		listsimanage:{
			listitems: {title:'The List'},
			listdetails: {title:'Edit Details'},
			listusers: {title:'Manage Users'}
		},
		mychildrenslists:{
			listitems: {title:'The List'},
			listdetails: {title:'Edit Details'},
			listusers: {title:'Manage Users'},
			managechildren: {title:'Manage Children'}
		},
		myfriendslists:{
			listitems: {title:'The List'},
//			listdetails: {title:'Edit Details'},
//			listusers: {title:'Manage Users'},
			findlist: {title:'Find a List'}
		}
	},
	loadingIndicator: "<img src='/lib/ext2/resources/images/default/grid/loading.gif' style='width:16px;height:16px;' align='absmiddle'>&#160;",
	loadingIndicatorSaving: "<img src='/lib/ext2/resources/images/default/grid/loading.gif' style='width:16px;height:16px;' align='absmiddle'>&#160;&nbsp;Saving...",
	loadingIndicatorLoading: "<img src='/lib/ext2/resources/images/default/grid/loading.gif' style='width:16px;height:16px;' align='absmiddle'>&#160;&nbsp;Loading...",
	loadingDiv: "<div style='width:100%;text-align:center;'><br /><br /><img src='/lib/ext2/resources/images/default/grid/loading.gif' style='width:16px;height:16px;' align='absmiddle'>&#160; Loading...</div>",
	
	// methods #######################################################
	getData: function(pars, callBackFn, loadingDiv, coded) {
		// shorthand for the AJAX call,
		// if its there loadingDiv gets a loading indicator till done - your callback has to remove it!
		// we add a UNIX timestamp to every call
		// pars can be a string, or an object to be turned into string
		if (Ext.type(pars)=='object') {pars = Ext.urlEncode(pars);}
		var coded = coded || false;
		var d = new Date();
		pars += "&_t="+Math.floor((d.getTime()/1000));
		pars += "&_to="+d.getTimezoneOffset()/(-60);
		if (loadingDiv !== undefined) {
			Ext.get(loadingDiv).update(oot.loadingIndicator + 'Loading...');
		}
		if (coded) {
			// turn the pars into a JSON string, and dhwCode them
			pars = Ext.urlEncode({enc:DHW.encode(Ext.encode(Ext.urlDecode(pars)))});
		}
		var myAjax = new Ajax.Request(
			oot.dataPort, 
			{
				method: 'post', 
				parameters: pars,
//				onComplete: callBackFn
				onComplete: function(o){
					// intercept any json string for de-compressing
					if (o.responseText != undefined) {
						o.responseText = oot.jsonComp.decode(o.responseText);
					}
					callBackFn(o);
				}
			});
	},
	getDataS: function(o) {
		/* a more sophisticated AJAX call / response; 'S' stands for 'Switch'
		 * calls dataport, expected a string reply, which is then 'switched'
		 * alternatively, can get a JSON reply of object: {result: data:}
		 * 		data is then available as this.rData
		 * call with object:
		  	pars: object or string of parameters for dataport
		  	fail: string - code number for fail message; dont specify if fail needs a function
		  		you can specify a result.fail as well as this, to have a callback
		  	result: {object collection of 'textResult':function}
		  		functions use 'this.field' as variables, where 'this' is object o
		  			this.msg will be the message box if already there
		  			this.rData is the json object from response text
		  	msg: an Ext.MessageBox object, [optional] - gets converted to 'please wait' - REMEMBER TO HIDE IT / USE IT!
		  		set it to false if you want NO waiting mask/message box
		  	maskDiv: an Ext.el of a div to be unmasked - alternative to msg - will be unmasked automatically
		  		if theres no msg or maskDiv, a 'please wait' messagebox is created as default
		   code: true / false - if true, we send data in encoded format
		 *  
		 *  ANYOTHERFIELDS you want the function to use
		 *  
		 *  if the dataport returns $$$ something, its encoded
		 */
		var msg;
		o.fail = o.fail || false;
		o.data = o.data || {};
		o.maskDiv = o.maskDiv || false;
		o.code = o.code || false;
		if (o.msg === undefined) {
			o.msg = (!o.maskDiv) ? Ext.MessageBox.wait("",'Please Wait...') : false;
		} else {
			if (o.msg !== false) {o.msg.show({progress:true, title:'Please Wait'});}
		}
		
		oot.getData(o.pars, function(ar){
			if (o.maskDiv) {o.maskDiv.unmask();}
			var r = ar.responseText;
			if (r.substr(0,3) == "$$$") {
				// decode it!
				var key = r.substr(3,3);
				r = r.substr(6);
				r = DHW.dhwCode(key,r);
			}
			// prepare an alert messageBox
			var msgO = {wait:false, buttons:Ext.MessageBox.OK}
			if (r == 'fail' && o.fail) {
				msgO.msg = "We are sorry, but there has been<br />a server problem, error code: " + o.fail;
				if (!o.msg) {
					Ext.MessageBox.alert('Server Problem', msgO.msg);
				} else {
					msgO.title = o.msg.title || "Server Problem";
					o.msg.show(msgO);
				}
				if (o.result !== undefined && o.result.fail !== undefined) {
					o.result.fail.call(o);
				}
				return false;
			}
			// see if we have JSON data, and convert it
			if (r.substr(0,1)=="{") {
				r = Ext.decode(r);
				// is it a compressed json?
				r = oot.jsonComp.decode(r);
				o.rData = r.data || false;
				r = r.result || "";
			}
			if (o.result != undefined && typeof o.result[r] == 'function') {
				// do the function thats in result[r], scope 'o'
				o.result[r].call(o);
			}
		}, undefined,o.code);
	},
	jsonComp: {
		// some stuff to deal with compressed json
		keys: {},
		decode: function(o){
			var OJ = oot.jsonComp;
			var isString = false;
			if (Ext.type(o) == 'string') {
				isString = true;
				if (o.substr(0,1) != "{" || o.indexOf('jsonk') == -1) {
					return o;
				}
				o = Ext.decode(o);
				if (o.jsonk == undefined) {
					return Ext.encode(o);
				}
			} else {
				// check its compressed
				if (o.jsonk == undefined) {
					return o;
				}
			}
			// transfer new keys
			if (o.jsonk.empty == undefined) {
				for (var k in o.jsonk) {
					if (OJ.keys[k] == undefined) {
						OJ.keys[k] = o.jsonk[k];
					}
				}
			}
			// convert the object
			var nObj = Ext.encode(o.data);
			var r;
			for (k in OJ.keys) {
				r = new RegExp(k,"gm")
				nObj = nObj.replace(r,OJ.keys[k])
			}
			if (isString) {
				return nObj
			} else {
				return Ext.decode(nObj)
			}
		}
	},
	getSiteContent: function(p, id) {
		// AJAX call to get html content for page name p, to go in element with id
		var myAjax = new Ajax.Request(
			"/content/" + p + ".php", 
			{
				method: 'get', 
				parameters: "",
				onComplete: function(o) {
					Ext.get(id).update(o.responseText);
				}
			});
		
	},
	menu: function(o) {
		// handles all the menu clicks
		var p = o.id.split("-");
		// make an object of parameters, we'll store this later
		var show = {sender: p.shift(), page:p.shift(), view:""};
		show.action = (p[0]!==undefined) ? p.shift() : "";
		show.data = (p[0]!==undefined) ? p.join("-") : "";

		if (show.page == 'logoff') {
			oot.show = show;
			oot.logOut();
			return true;
		}
		// are we actually moving?
		if (oot.show !== undefined && DHW.compareObj(oot.show, show)) {
			// no, so don't do anything
			return true;
		}
		// set the flag
		oot.tabActivatedByMenu = true;
		// do we need a whole page or just a tab loading?
		show.view = (oot.show !== undefined && oot.show.page !== undefined && oot.show.page == show.page && oot.show.page !=='myfriendslists') ? "tab" : "page"; 
		var dataChanged = (oot.show.data != show.data);
		oot.show = show;
		if (show.view == "tab") {
			var tabId = "ootab-"+show.page+"-"+show.action;
			if (oot.currentTab == tabId && dataChanged) {
				// we need to refresh the tab, it's already activated
				oot.loadTab.call(oot.tabPanel.getTab(tabId));
			} else if (oot.currentTab != tabId){
				oot.tabPanel.activate(tabId);
			} else {
				oot.tabActivatedByMenu = false;	// reset the flag, we're not activating anything!
			}
		} else {
			var pars = "p=" + show.page + "&a=" + show.action + "&v=" + show.view+ "&d=" + show.data;
			var div = Ext.get('content-inner').load(oot.contentPath,pars,oot.pageLoad);
		}
	},
	logOut: function(dontCheck) {
		// the form mainLogout is on every page
		// call with true to just log straight out
		var dontCheck = dontCheck || false;
		if (dontCheck) {document.mainLogout.submit();}
		else if (confirm("Are you sure you want to log out?")) {
			document.mainLogout.submit();
		}
	},
	layoutStore:{},
	createTabs: function(show) {
		// creates the tabs for page, show is the classic 'show' object
		// info about the tabs is in oot.pageInfo
		// get the div
		var page = show.page, L;
		var pInfo = oot.pageInfo[page];		

		// have we already got a layout?
		var layId = 'ootPage-'+page+'-layout';
		if (pInfo.layout !== undefined) {
//		if (pInfo.layout !== undefined && show.action != "refresh") {
			var L = pInfo.layout;
			// show the tab
			L.setActiveTab('ootab-'+show.page+'-'+show.action)
		} else {
			// create the layout
			
			// make an array of tabs
			var tabAr = [];
			var tabId = 'ootab-'+page+'-';			
			var loadingInfo = "<div style='text-align:center; padding-top:50px'>"+oot.loadingIndicator+" Loading...</div>";			
			for (var tab in pInfo.tabs) {
				if (pInfo.tabs[tab].tabTest === undefined || pInfo.tabs[tab].tabTest()) {
					// store the 1st tab as the view, as default
					if (show.action === "") {show.action = tab;}
					// create the tab
					var thisTabO = {
						id: tabId+tab,
						title: pInfo.tabs[tab].title,
						iconCls:(pInfo.tabs[tab].icon !== undefined) ? 'oot-icon-'+ pInfo.tabs[tab].icon : "",
						listeners: {activate: oot.loadTab},
						cls: 'oot-listtab',
						layout:'fit'
					};
					if (pInfo.tabs[tab].config !== undefined) {
						thisTabO = Object.extend(thisTabO, pInfo.tabs[tab].config);
					}
					tabAr.push(thisTabO);
				}
			}
			// add a tablayout to the page panel
			L = Ext.getCmp('ootPage-'+page).add(new Ext.TabPanel({
				autoScroll: true,
				// resizeTabs: true,	// for some reason this actually fixed tab width - doesnt work with icons
				minTabWidth: 50,
				tabPosition:'top',
				header:true,
				items: tabAr, border:false,
				activeTab: tabId+show.action
			}));
			pInfo.layout = L;
			Ext.getCmp('ootPage-'+page).doLayout();

		}
	},

	createTabs3: function(show) {
		// creates the tabs for page, show is the classic 'show' object
		// try doing this in a borderlayout?
		// info about the tabs is in oot.pageInfo
		// get the div
		var page = show.page;
		var pDiv = Ext.get('ootPage-'+page);

		// is it empty? or are we refreshing?
		if (pDiv.dom.innerHTML !== "" && show.action != "refresh") {return false;}

		// get the info for this page, and make some basic div creations
		var pInfo = oot.pageInfo[page];
		pDiv.update("");
		pDiv.createChild({tag:'h2', html:pInfo.title});
		pDiv.createChild({id:'ootPage-'+page+'-tabs'});
		
		// make and populate the tabpanel
		pInfo.tabPanel = new Ext.TabPanel('ootPage-'+page+'-tabs');
		var pT = pInfo.tabPanel, thisTab, tabInfo, tabId = 'ootab-'+page+'-';	// shorthand!
		for (var tab in pInfo.tabs) {
			// store the 1st tab as the view, as default
			if (show.action === "") {show.action = tab;}
			tabInfo = pInfo.tabs[tab];
			// create the tab
			
			thisTab = pT.addTab(tabId+tab, tabInfo.title);
			// register on-activate
			thisTab.on('activate', oot.loadTab, thisTab);
			// store it
			tabInfo.tab = thisTab;
		}
		menu.now = Object.clone(show);
		pT.activate(tabId + show.action);
	},
	
	createTabs2: function() {
		// looks for a div called 'oot-page-tabs', and converts it to tabs
		if ($('oot-page-tabs') === null) {return false;}
		oot.tabPanel = new Ext.TabPanel("oot-page-tabs");
		var t = oot.tabPanel;
		var divs = $('oot-page-tabs').getElementsByTagName('div');
		var divsList = $A(divs);
		divsList.each(function(d) {
			var tabId = $(d).id;
			var tabIdAr = tabId.split("-");
			// id is in format: ootab-pagename-tabname
			if (tabIdAr[0] == "ootab") {
				if (oot.show.action === "") {oot.show.action = tabIdAr[2];}	// store the 1st tab as the view		
				var T = t.addTab($(d).id, oot.tabInfo[oot.show.page][tabIdAr[2]].title);
//				var updater = T.getUpdateManager(); // see Ext.TabPanel
//				updater.setDefaultUrl(oot.contentPath+"?p="+oot.show.page + "&a=" + tabIdAr[2] + "&v=tab&d=");
//				T.on('activate', updater.refresh, updater, true);

				/* set the first Url for this tab,
				 * but only if its not the one chosen by a menu click
				 * otherwise the updated fires twice for this tab */
				if (!(oot.tabActivatedByMenu && tabIdAr[2]==oot.show.action)) {
					var pars = "p=" + oot.show.page + "&a=" + tabIdAr[2] + "&v=tab&d=" + oot.show.data; // added the oot.show.data - not sure if i should have
					T.setUrl(oot.contentPath, pars);
				}
				// set the update and activate calls
				var updater = T.getUpdateManager(); // see Ext.TabPanel
				updater.on('update', oot.updateTab, T);
				T.on('activate', oot.loadTab, T);
				oot.tabs[tabId] = T;
			}
		});
		t.activate("ootab-"+oot.show.page + "-" + oot.show.action);
	},
	pageLoad: function() {
		// the callback when a page has been loaded by a menu click
		oot.createTabs();
		switch (oot.show.page) {
			case "home":
				home.ready();
			break;
			default:
				listPage.ready();
			break;
		}

		
	},
	loadTab: function(t) {
		var tabId = t.getId();
		tabData = tabId.split("-");
		if (menu.now.action !== "" && menu.now.action != tabData[2]) {
			// this has been called by pressing a tab
			var gt = Object.clone(menu.now);
			gt.action = tabData[2];
			menu.go(gt);
		}
		return true;
	},
	updateTab: function(t) {
		// this is called when a tab has updated
		var tabId = this.id;
		var tabIdAr = tabId.split("-");
		// get the tabdata
		oot.tabData = DHW.getJSON('oot-tabdata');
		// what we need to prepare in the tab depends on that tab...
		var page = tabIdAr[1];
		var tab = tabIdAr[2];
		switch (page) {
			case 'myaccount':
				myaccount.tabReady(tab, this);
			break;
			default:
				listPage.tabReady(page, tab, this);
			break;
		}
	},
	renderDate: function(d) {
alert('change oot.renderDate');
		return oR.date(d);
	},
	renderPrice: function(v) {
alert('change oot.renderPrice');
		return oR.price(v);
		},
	createButton: function(buttonO) {
		/* makes one button, if the holding div exists
		 * buttonO.id = holding div id
		 * 	.config = button config obj */
		var el = $(buttonO.id);
		if (el === null) {return false;}
		if (el.innerHTML.substr(0,1) == "{") {
			// this button object has config JSON in it
			buttonO.info = DHW.getJSON(buttonO.id);
			el.innerHTML = "";
			var d = Ext.get(buttonO.id);
			d.createChild({
				tag:'span',
				id:buttonO.id+"-label",
				cls:'oot-button-label',
				html:buttonO.info.label
			});
			d.createChild({
				tag:'span',
				id:buttonO.id+"-button",
				cls:'oot-button-button'
			});
			buttonO.config.renderTo = buttonO.id+"-button";
			return new Ext.Button(buttonO.config);
		} else {
			buttonO.config.renderTo = buttonO.id;
			return new Ext.Button(buttonO.config);
		}
	},
	loadedScripts:{home:''}, // home is already loaded!
	loadScript: function(page, menuO) {
		// loads the script for a page, and then does a menu.go to menuO
		if (oot.loadedScripts[page]!== undefined) {menu.go(menuO);return false;}
		oot.loadedScripts[page] = {m: menuO};
		var r = "<script type='text/javascript' language='JavaScript' src='/lib/pages/" + page + ".js?v=" +jsds[page] + "'></script>";
		Ext.get('oot-loadScript').update(r, true);
	},
	loadScriptDone: function(page) {
		// call this at the bottom of the script you are loading, 
		// with the appropriate page name
		menu.go(oot.loadedScripts[page].m);		
/*
		Ext.get('oot-loadScript-maskDiv').hide({callback:function() {
			$('oot-loadScript-mask').remove();
			menu.go(oot.loadedScripts[page].m);
		}});
*/		
	},
	error: function(code, mb, fn) {
		// reports an error code, in the messageBox mb if included, with callback fn if included
		var m = "We're sorry, but there has been a server problem.<br />Error Code: "+code;
		if (mb !== undefined && mb.show !== undefined) {
			mb.show({
				wait:false,prompt:false,buttons:Ext.MessageBox.OK,title:'Server Error',
				msg:m,'fn':(fn || false)
			});
		} else {
			Ext.MessageBox.alert('Server Error',m,fn);
		}
	},
	mT: function(t) {
		// Make Title: turns this_that into This That
		if (t.indexOf("_")==-1) {return t.capitalize();}
		var a = t.split("_");
		a = a.collect(function(b) {
			return b.capitalize();
		});
		return a.join(" ");
	},
	removeAllPanels: function(p) {
		// removes all component panels from Ext Panel p
		var c = 0;
		while (p.items !== undefined  && p.getComponent(c) !== undefined) {
			p.remove(c,true);
			c++;
		}
		return p;
	},
	iconHTML: function(icon) {
		// icon is a file reference (from icons.something, eg)
		return "<img src='" + icon + "' class='oot-icon'>";
	},
	// advertising functions etc
	ads: {
		update: function(lNum) {
			Ext.get('oot-south').update("<iframe src='/listbycode/" + lNum + "' height='15' frameborder='0' width='468' scrolling='no' allowtransparency='true' hspace='0' vspace='0' marginheight='0' marginwidth='0'></iframe>")
		}
	},
		// some stuff to do with popup images
	imagePopup: {
		show: function(ref) {
			var el = Ext.getBody().createChild();
			var w = new Ext.Window({
				applyTo:el,
				height:"50%", width:"50%", constrain:true, modal:true,
				autoHeight: true,
				closeAction :'close',
				plain       : true,
				html: "<img src='" + ref + "' style='width:100%'>",				
				buttons: [{
					text: 'Close',
						handler  : function(){
						w.destroy();
					}
				}]
			});
			w.show();
		},
		addEventToClass: function(cls) {
			// adds the onclick to all images of class cls
			$$('img.' + cls).each(function(el){
				el.observe('click', function(){
					var src = el.src.replace(/_200/, "");	// removes the _200 bit to give the bigger image!
					oot.imagePopup.show(src);
				});
				el.style.cursor = "pointer";
			});
		}
	}
};
// eof OOT object ##############################################################
// eof OOT object ##############################################################
var user = {
	// data to do with the user
	loggedIn: false,
	checkManagerInvites: function(listNum) {
		// has the user been invited to be a manager?
		// its in menu.data.managerInvites, object of {num1:name1, num2:name2 etc}
		// if listNum is given, we check if hes been invited to manage list listNum
		if (menu.data.managerInvites === undefined || Ext.type(menu.data.managerInvites) != 'object') {
			return false;
		}
		// first if listNum is given, do the check
		if (listNum !== undefined) {
			return (menu.data.managerInvites[listNum] !== undefined);
		}
		// listNum wasnt given, alert them that there are lists to check.
		if (menu.data.managerInvites !== undefined && Ext.type(menu.data.managerInvites)=='object') {
			Ext.MessageBox.confirm("Manager Invitations",
			"You've been invited to be the manager of a list."
			+ "<br />Would you like to view 'My Account' to check it out?",
			function(o){
				if (o == 'yes') {
					menu.go({page:'myaccount'});
				}
			});
		}
	},
	viewManagerInvite: function(listNum) {
		// this passes the user on to listpage stuff, so they can accept / decline manager invitation
		menu.go({page:'myfriendslists', action:'listitems', data:listNum});
	},
	viewInvitation: function(o, invButton) {
		// do this by dialog, rather than showing the list
		// - they should be able to work out if they want to view the list from some details...
		// o is an object of list details, invButton is the button object that was pressed (or undefined if called from elsewhere)
		var invButton = invButton || {hide: function(){}};	// an empty-ish function if its not defined
		Ext.Msg.show({
		   title:"Accept List Invitation?",
		   msg: "<b>" + o.known_to_user_as + "</b> has invited you to view <br /><b>" + o.listNum
			+ ", " + o.title
			+ "</b><br />Would you like to accept this invitation?",
		   buttons: Ext.Msg.YESNOCANCEL,
		   icon: Ext.MessageBox.QUESTION,
   		   fn: function(btn){
				if (btn != 'cancel') {
					oot.getDataS({
						pars:{cmd:'acceptinvitation',value:btn,'list_id':o.list_id},
						fail: "vwI-1",
						result: {
							accepted: function(){
								this.msg.hide();
								invButton.hide();
								Ext.Msg.confirm("Invitation Accepted",
									"You have successfully accepted the invitation, would you like to view the list?",
									function(r){
										if (r == 'yes') {
											// reset the list data if we have come via the oothanks/LIST-NUMB route
											if (typeof(listPage) !== "undefined" && listPage.listData !== undefined) {
												listPage.listData = "";
											}
											menu.go({page:'myfriendslists', action:'listitems', data:o.listNum});
										} else {
											menu.go({page:'myaccount'});
										}
									}
								)
							},
							declined: function(){
								this.msg.hide();
								invButton.hide();
								Ext.Msg.alert("Invitation Declined",
									"You have successfully declined the invitation."
								)
								menu.go({page:'myaccount'});
							},
							listcompleted: function(){
								this.msg.hide();
								invButton.hide();
								Ext.Msg.alert("List Completed",
									"Sorry, that list has already been completed."
								)
								menu.go({page:'myaccount'});
							}
						}
						
					});
				} else {
					menu.go({page:'myaccount'});
				}
		   }
		});
	},
	// other manageInvite functions are listPage.managerInvite
	checkListInvitations: function(t) {
		// checks to see if the user has been invited to view any lists
		// t can be "alert", for a message to be displayed for true, or blank.
		// a list of invitations will be returned		
		if (menu.data.invitations !== undefined && menu.data.invitations !== "") {
			if (t !== undefined && t == "alert") {
				var d = "", comma = "", MDI = menu.data.invitations, nI = MDI.length;
				MDI.each(function(inv, i){
					d += comma + "<b>"+inv.known_to_user_as + "</b> has invited you to view <b>" + inv.listNum + ": " + inv.title + "</b>";
					comma = ", <br />\n";
				});
				d += ", <br />Visit 'My Account - View' to review " + ((nI == 1) ? "this" : "these") + " invitation" + ((nI == 1) ? "." : "s.");

				Ext.Msg.alert("List Invitations", d);
			}
			return menu.data.invitations;
		} else {
			return false;
		}
	},
	checkListInvite: function(listN) {
		// has the user been invited to view list?
		// return false, or an object of info
		var MDI = menu.data.invitations || "";
		if (MDI == "") {return false;}
		var res = MDI.find(function(inv){
			return (inv.listNum == listN);
		});	
		return res || false;
	},
	// functions to do with friends
	friends: {
		data: '',	// store for data to come, in the form id:{object of data}
		add: function(store, listNum) {
			// add a new friend, and add it to the store
			// if listNum is defined, we store it so dataport can work out the friend owner
			user.friends.addListNum = listNum;
			var rec = new store.recordType({});
			user.friends.edit(rec, store);
		},
		deleteSelected: function(){
			// delete the selected friends
			user.friends.withSelected('delete');
		},
		edit: function(friendRec, store, callback){
			// dialog to edit friend data
			// callback will be called with (data changed, record)
			
			var isNew = (friendRec.get('friend_id') == undefined);
			var UF = user.friends;
			UF.editCallback = (callback !== undefined) ? callback : function(){};
			UF.editStore = store;
			
			// get the friend info
			var thisFriend = friendRec;
			
			// make the form layout data first
			var ms = ((isNew) ? "Enter " : "Edit ") + "this friend's information";
			var fmData = [
				{type: 'text', config: {style:'padding:5px', html: ms}},
				'friend_name',
				'known_to_friend_as',
				'friend_email'
			];
			if (isNew) {
				fmData = fmData.concat([
					{type:'text', config:{style:'padding:5px',html:"Please enter the picture code. This is intended to discourage people from mis-using this feature (sorry!)."}},
					'captcha_attempt'
				]);
			}
			// now make the dialog object
			var bt = (isNew) ? ['Save & Add Another', 'Save & Close', 'Cancel'] : ['Save', 'Cancel'];
			var dfO = {
				type:'dialog',
				id:'oot-friends-edit',
				config:{title:((isNew) ? "New" : "Edit") + ' Friend Information', width:370, height:240},
				keys:[27],buttons:bt,
				handler:UF.editHandler,
				content: {
					type:'form',
					config:{name:'editFriend', labelWidth:150, id:'editFriend'},
					data:fmData
				}
			};
			
			// and render the dialog
			UF.editDlg = content.renderDialog(dfO);
			UF.editingFriend = thisFriend;
			
			// and fill the forms fields
			var form = Ext.getCmp('editFriend').getForm();
			form.loadRecord(thisFriend);			
		},
		editClick: function(fId){
			// click handler for edit grid
			// we have to work out which grid we are looking at...
			// this comes from the current page info
			var store = (menu.now.page == 'myaccount') ? myaccount.friends.store : listPage.usersStore.friends;
			var rec = store.getById(fId);
			if (rec != undefined) {
				user.friends.edit(rec,store);
			} else {
				Ext.MessageBox.alert('Error','There has been an error finding the record for this friend. Sorry!');
			}
		},
		editHandler: function(r) {
			var UF = user.friends;
			var thisFriend = UF.editingFriend;	// a record
			var isNew = (thisFriend.get('friend_id') == undefined);
			var dlg = this.ootContent;
			
			switch(r.text.substr(0,4)) {
				case 'Save':
					var form = Ext.getCmp('editFriend').getForm();
					if (form.isValid()) {
						if (form.isDirty()) {
							var v = form.getValues();
							// sort out new|old values
							var p = {};
							for (var f in v) {
								p[f] = v[f] + '|' + thisFriend.get(f);
							}
							p.cmd = 'updatefriend';
							p.captcha_image = (dlg.ootCaptchaImage !== undefined) ? dlg.ootCaptchaImage : false; 
							p.isnew = (isNew) ? 'true' : 'false';
							if (UF.addListNum !== undefined) {
								p.list_num = UF.addListNum;
							}
							p.friend_id = thisFriend.get('friend_id') + '|0';
							oot.getDataS({
								pars: p,
								fail: "eFr-1",
								result: {
									alreadyfriend: function(){
										this.msg.hide();
										Ext.MessageBox.alert('Add Friend', 'That person is already on your Friends &amp; Family list!');
									},
									captchawrong: function(){
										this.msg.hide();
										Ext.MessageBox.alert('Picture Code', 'Sorry, but you got the picture code wrong. Please try again.');
										content.updateCaptcha(dlg, 'oot-friends-edit-content');
									},
									success: function(){
										this.msg.hide();
										form.updateRecord(thisFriend);
										if (isNew) {
											// set the new friend_id
											thisFriend.set('friend_id', this.rData);
										}
										thisFriend.commit();
										if (isNew) {
											// add to the store
											UF.editStore.add(thisFriend);
										}
										if (r.text == 'Save & Add Another') {
											// clear the dialog to add another friend
											var newF = new UF.editStore.recordType({
												friend_email: '',
												friend_name:'',
												known_to_friend_as:''
											});
											form.reset();
											UF.editingFriend = newF;
											content.updateCaptcha(dlg, 'oot-friends-edit-content');
										} else {
											UF.editDlg.close();
											UF.editCallback(true, thisFriend);	
										}
										
									}
								}
							});
						} else {
							if (r.text == 'Save & Add Another') {
								// clear the dialog to add another friend
								var newF = new store.recordType({});
								form.loadRecord(newF);
								UF.editingFriend = newF;
							} else {
								UF.editDlg.close();
								UF.editCallback(false, thisFriend);
							}
						}
					}
					
				break;
				case 'Canc':	// for 'Cancel'
					UF.editDlg.close();
					UF.editCallback(false, thisFriend);
				break;
			}			
		},
		inviteSelected: function(){
			// delete the selected friends
			user.friends.withSelected('invite');
		},
		
		selectAll: function(){
			// find all the .oot-friends-checkbox, and either select or deselect all of them
			// if all are selected, we clear all, otherwise we set all
			var cbs = user.friends.selectCheckBoxes();
			var allAreSelected = (undefined == cbs.find(function(el){
				return !el.checked;
			}));
			cbs.each(function(el){
				el.checked = !allAreSelected;
			});
		},
		selectCheckBoxes: function(){
			// utility to select the appropriate .oot-friends-checkbox checkboxes
			// we only want the ones inside a certain div, which could be one of two...
			var divId = (menu.now.page == 'myaccount') ? 'ootab-myaccount-friendsfamily-friendGrid':'ootab-listsiown-listusers-userGrid-friends';
			return $$('#' + divId + ' .oot-friends-checkbox');
		},
		withSelected: function(action) {
			var cbs = user.friends.selectCheckBoxes();
			var sel = cbs.findAll(function(el){
				return el.checked;
			});
			// have any been selected?
			if (sel.length == 0) {
				return false;
			}
			// confirm the action
			var msg = (action == 'delete') ? 
				"Are you sure that you want to delete the selected friends? (This cannot be undone)"
				: "Click YES to invite the selected friends.";
			var title = (action == 'delete') ? "Delete Selected Friends" : "Invite Selected Friends";
			Ext.MessageBox.confirm(title, msg, function(r){
				if (r == 'yes') {
					// make a list of the ids
					var idlist = [];
					sel.each(function(el){
						idlist.push(Number(el.id.replace(/^[a-zA-Z\-]+/,'')));
					});
					var p = {cmd:'friends'+action, list:idlist.join('|')};
					if (action == 'invite') {
						// we need the list number, from listPage.listNum
						p.list_num = listPage.listNum;
					}
					oot.getDataS({
						pars: p,
						fail: 'frID-1',
						result: {
							emailfail: function(){
								this.msg.hide();
								var n = this.rData.length;
								var m = 'We were unable to send the following email'
									+ ((n > 1) ? 's' : '') + ':<br /><b>'
									+ this.rData.join(', ')
									+ '</b><br />and so '
									+ ((n > 1) ? 'those people have' : 'that person has')
									+ ' not been invited.';
								Ext.MessageBox.alert('Email errors', m);
							},
							fail: function(){
								this.msg.hide();
								Ext.MessageBox.alert('Error','Sorry, there has been an error, please refresh this page and try again.');
							},
							success: function(){
								this.msg.hide();
								// now refresh the current view
								menu.goNow();
							}
						}
					});
				} 
			});
		}
	}
};
// eof USER object ################################################################

var menu = {
	// stuff to do with navigation
	/* each view can be uniquely defined with the parameters:
	 * 		page:	home,myaccount,listsiown,listsimanage,myfriendslists,newaccount,login,findlist
	 * 		action:	on most occasions this is the tabname, on the given page, but it can also be newlist,findlist for dialog boxes
	 * 		view:	may now be redundant, used to specify page/tab for the AJAX upload
	 * 		data:	usually the list number
	 */
	now: {page:"", action:"", view:"", data: ""},	// the currently shown view
	data: "",	// stores the data about menu structure

	handleMenu: function(o) {
		// handles all the menu clicks
		// o.id comes in as [sender]-[page]-[action]-[data]
		var p = o.id.split("-");
		// make an object of parameters, we'll store this later
		var show = {sender: p.shift(), page:p.shift(), view:""};
		show.action = (p[0]!==undefined) ? p.shift() : "";
		show.data = (p[0]!==undefined) ? p.join("-") : "";

		if (show.page == 'logoff') {
			home.logOut();
			return true;
		}
		if (show.page == 'login') {
			login.box.show();
			return true;
		}
		if (show.page == 'help') {
			menu.handleHelp(show.action);
			return true;
		}
		// are we actually moving?
		if (DHW.compareObj(menu.now, show)) {
			// no, so don't do anything
			return true;
		}
		menu.go(show);
		return true;
	},
	draw: function(){
		// shows the menu based on the data
		// our data is in id=ootData-topmenu
		// how about getting menu data by JSON?
		if (menu.data === "") {
			oot.getDataS({
				pars:{cmd:'refreshmenu'}, fail:'mdr-1', msg:false,
				result: {
					success: function() {
						menu.data = this.rData;
						menu.draw();
					}
				}
			});
			return false;
		}
		var MD = menu.data;
		
		/* data format: this format needs changing to reflect the menu structure, but still works
		 * 	myAccount:"" (blank, but its existence means we're logged in)
		 * 	myLists:owner:{array: list_num=>{title:, status:}}
		 * 		manager:{array: list_num=>{title:, status:}}
		 * 		children:[array of child objects: {name, id, lists:{array: list_num=>{title:, status:}})
		 * 	myFriendsLists: {array: list_num=>{title:, status:}}
		 * 
		 * id format:
		 * topmenu-menubuttonname-itemname[-listnum]
		 */
		
		$('topmenu').innerHTML = "";
		$('topmenu2').innerHTML = "";
		var tb = new Ext.Toolbar({renderTo:'topmenu2'});
//		var tb = Ext.getCmp('viewport-centre').getTopToolbar();
		var menuItems, listMenu, lNum, menuType, thisMenu, newMenu, menuTitle, menuItem, newExMenu, exMenuItem, exMenuTitle, tbComma;	
	
		// Home is added in the initial config
		tb.addButton({
			text: 'Home',
			id: 'topmenu-home',
			handler: menu.handleMenu,
			tooltip: {text:'Back to the Welcome page.', title:'Home', autoHide:true}
		});	
		// My Account; needs a title, and a dropdown menu
		if (MD.myAccount !== undefined) {
			tb.add("-");
			// we have to create an array first, so that we can insert managechildren appropriately
			var mAmenuItems = [
				{text:'View', id:'topmenu-myaccount-view', handler:menu.handleMenu, icon: icons.bookOpen},
				"-"
			];
			if (MD.myLists !== undefined && MD.myLists.children !== undefined) {
				mAmenuItems.push(
					{text:'Manage Children', id:'topmenu-myaccount-managechildren', handler:menu.handleMenu, icon: icons.manageChildren},
					"-"
				)
			}
			mAmenuItems.push(
				{text:'Friends &amp; Family', id:'topmenu-myaccount-friendsfamily', handler:menu.handleMenu, icon: icons.friends},
				{text:'Buying / Bought', id:'topmenu-myaccount-items', handler:menu.handleMenu, icon: icons.basket},
				{text:'To Do...', id:'topmenu-myaccount-todo', handler:menu.handleMenu, icon: icons.tick},
				"-",
				{text:'Log Off', id:'topmenu-myaccount-logoff', handler:menu.handleMenu, icon:icons.logout}
			);
			tb.add({
				text:"My Account",
				tooltip:{text: "Check your account overview, edit details, etc.", title:"My Account", autoHide:true},
				menu:{
					items: mAmenuItems 
				}
			});
		}
		// My Lists
		if (MD.myAccount !== undefined) {
			thisMenu = new Ext.menu.Menu();
	
			if (MD.myLists !== undefined) {
				var m = MD.myLists;
				// add the owner and manager lists
				menuTitle = {
					owner:["My Lists","listsiown", "View and manage your own lists, or add a new one."],
					manager:["Lists I Manage","listsimanage", "Manage your friends' lists for them."]};
				for (var t in menuTitle) {
					if (t == "owner" || m[t] !== undefined) {
						listMenu = new Ext.menu.Menu();
						if (t == "owner") {
							// shall we let them add a list? DHW.countObject(m.owner)
							if (MD.numberCurrentLists === undefined || MD.numberCurrentLists < LIMITS.maxLists) {
								listMenu.add({
									text: 'New...',
									id: 'topmenu-myaccount-newlist',
									handler: menu.handleMenu,
									icon: icons.newList
								}, "-");
							}
						}
						for (lNum in m[t]) {
							listMenu.add({
								id: 'topmenu-' + menuTitle[t][1] + '-listitems-' + lNum,
								text: m[t][lNum].title,
								handler: menu.handleMenu,
								icon: icons.list
							});
						}
						tb.add("-", {
							text: menuTitle[t][0],
							tooltip: {text:menuTitle[t][2],title:menuTitle[t][0]},
							menu: listMenu
						});
					}
				}
				// and now the children's lists
				if (m.children !== undefined) {
					thisMenu = new Ext.menu.Menu();
					thisMenu.add({
						text: 'Manage your children (!)',
						id: 'topmenu-myaccount-managechildren',
						handler: menu.handleMenu,
						icon: icons.manageChildren
					}, "-");
					if (m.children !== "") {
						var childIcon = 0;
						m.children.each(function(childO){
							// childO is an object
							listMenu = new Ext.menu.Menu();
							if (childO.lists == "") {
								// add a 'no lists' blank item
								listMenu.add({
									text: 'no lists',
									disabled: true
								});
							} else {
								for (lNum in childO.lists) {
									listMenu.add({
										id: 'topmenu-mychildrenslists-listitems-' + lNum,
										handler: menu.handleMenu,
										text: childO.lists[lNum].title,
										icon: icons.list
									});
								}
							}
							thisMenu.add({
								text: childO.name,
								menu: listMenu,
								icon: icons['child'+childIcon]
							});
							childIcon = (childIcon + 1) % 3;
						});
					}
					tb.add("-", {
						text: "My Children's Lists",
						tooltip: {text:"View and manage your children's lists, add a new list, or log in as one of them.", title:"My Children's Lists", autoHide:true},
						menu: thisMenu
					});
				}
			}
		}
	
		// My Friends Lists
		if (MD.myAccount !== undefined) {
			thisMenu = new Ext.menu.Menu();
			// add a fancy find list menu
			thisMenu.add({
				text:'Find A List...',
				handler: menu.handleMenu,
				id:'topmenu-myfriendslists-findlist',
				icon: icons.find
			},"-");
			// add the list of friends lists
			if (MD.myFriendsLists !== undefined) {
				var MDF = MD.myFriendsLists, fMenu;
				for (var owN in MDF) {
					fMenu = new Ext.menu.Menu();
					for (lNum in MDF[owN]) {
						fMenu.add({
							id: 'topmenu-myfriendslists-listitems-'+lNum,
							handler: menu.handleMenu,
							text: MDF[owN][lNum].title,
							icon: icons.list						
						});
					}
					thisMenu.add({
						text: owN,
						menu: fMenu,
						icon: icons.person
					});
				}
			}
			tb.add("-", {
				text: "My Friends' Lists",
				tooltip: {text:"Find and View your friends' lists.", title:"My Friends' Lists", autoHide:true},
				menu: thisMenu
			});
		}
		// Log Off
		if (MD.myAccount !== undefined) {	
			tb.add("-", new Ext.Toolbar.Button({
				text: 'Log Off',
				id: 'topmenu-logoff',
				handler: menu.handleMenu,
				tooltip: {text:'Log yourself out, and return to the homepage.', title: "Log Out", autoHide:true}
			}));
		} else {
			tb.add("-", new Ext.Toolbar.Button({
				text: 'Log In',
				id: 'topmenu-login',
				handler: menu.handleMenu,
				tooltip: {text:'Log In, so that you can access the lists.', title: "Log in", autoHide:true}
			}));
		}
		
		// HELP!
		var	helpMenu = new Ext.menu.Menu();
			// add a fancy find list menu
		helpMenu.add(
			{
				text:'Getting Started',
				handler: menu.handleMenu,
				id:'topmenu-help-start',
				icon: icons.gettingStarted
			},
			{
				text:'ooHELP! site',
				handler: menu.handleMenu,
				id:'topmenu-help-site',
				icon: icons.help
			},"-",
			{
				text:'News',
				handler: menu.handleMenu,
				id:'topmenu-help-news',
				icon: icons.news
			},
			{
				text:'How To... & F.A.Q.',
				handler: menu.handleMenu,
				id:'topmenu-help-howto',
				icon: icons.howTo
			},
			{
				text:'Ask a Question',
				handler: menu.handleMenu,
				id:'topmenu-help-ask',
				icon: icons.ask
			},
			{
				text:'Report a Bug',
				handler: menu.handleMenu,
				id:'topmenu-help-bugs',
				icon: icons.bug
			}
		);

		tb.add("-", {
			text: "Help",
			tooltip:{text:"This is where to get some help.", title:"Help"},
			menu: helpMenu
		});
		oot.viewport.doLayout();
		// DO NOT check invitations: the user goes straight to myaccount when they log in, so will see them.
		// check invitations
		//		user.checkListInvitations('alert');	// before managerinvites: this one only alerts, it doesn't send you anywhere
		// what about manager invites?
		//	user.checkManagerInvites();		
	},
	handleHelp: function(h) {
		// sends people to help pages
		var newLoc = "http://help.oothanks.com/";
		var vf = "viewforum.php?f=";
		var g2 = "";
		switch(h) {
			case 'site':
				g2 = newLoc;
			break;
			case 'start':
alert("to do...! (Sorry)");
			break;
			default:
				var pages = {news:'1',howto:'2',ask:'3',bugs:'4'};
				g2 = newLoc + vf + pages[h];
			break;
		};
		if (g2 !== "") {
			window.open(g2);
		}
	},
	refresh: function(callbackFunction) {
		// refresh the menu details
		// if specified, itll call a callback next
		menu.data = "";
		oot.getDataS({
			pars:{cmd:'refreshmenu'}, fail:'mdr-1', msg:false,
			result: {
				success: function() {
					menu.data = this.rData;
					menu.draw();
					if (callbackFunction !== undefined) {
						callbackFunction();
					}
				}
			}
		});
//		menu.draw();		
	},
	// an array of the script files that pages need to load
	scriptForPage: {
		home: 'home',
		newaccount:'newaccount',
		myaccount:'myaccount',
		listsiown:'listpage',
		listsimanage:'listpage',
		mychildrenslists: 'listpage',
		myfriendslists:'listpage'
	},
	go: function(o) {
		// moves us to the page described in object o;
		o.page = o.page || "home";
		o.action = o.action || "";
		o.view = o.view || "";
		o.data = o.data || "";
		
		// logging
		if (o.page != menu.now.page || o.action != menu.now.action || o.view != menu.now.view || o.data != menu.now.data) {
			var logpars = {cmd:'log'};
			logpars = Object.extend(logpars, o);
			oot.getDataS({
				'pars':logpars, fail:'log-1',msg:false,code:true
			});
		}
		// check if the user is activated
		if (menu.data.myAccount !== undefined && menu.data.myLists === undefined) {
			// un-activated, so can only visit home and myaccount-view
			if (o.page !== 'home' && o.page !== 'myaccount') {
				o.page = 'myaccount';
			}
			o.action = "";
			o.view = "";
			o.data = "";
		}

		// myfriendslists-findlist is a special case
		if (o.action == 'findlist') {
			viewList.enterListNum();
			return true;
		}
		// as is myaccount-newlist
		if (o.action == 'newlist') {
			if (typeof myaccount == 'undefined') {
				oot.loadScript('myaccount',o);
				return true;
			}
			myaccount.newList.click();
			return true;		
		}
		// for newaccount, check were not logged in 
		if (o.page == 'newaccount' && user.loggedIn !== undefined && user.loggedIn) {
			Ext.MessageBox.alert('New Account',"You need to be logged out to create a new account");
			return false;
		}
		// each page where the script is not loaded has its own 'loading' image,
		// so we can load the script once the page has changed to the new page
		if (menu.now.page == o.page && oot.loadedScripts[menu.scriptForPage[o.page]]===undefined) {
			oot.loadScript(menu.scriptForPage[o.page], o);
			return true;
		}
		
		// do we need to hide / show pages?
		if (o.page !== menu.now.page) {
			Ext.getCmp('card-holder').layout.setActiveItem('ootPage-'+o.page);
			menu.now.page = o.page;
			menu.go(o);
			return true;
		}
		menu.now = Object.clone(o);
		
		switch (o.page) {
			case 'home':
				menu.history.hash(o);
				home.show(o);
			break;
			case 'newaccount':
				menu.history.hash(o);
				newaccount.show(o);
			break;
			case 'myaccount':
				menu.history.hash(o);
				myaccount.show(o);
			break;
			default:
				// do the south panel
				// save history for later...
				if (o.data.substr(4,1)=='-') {
					oot.ads.update(o.data)
				}
				listPage.show(o);
			break;
		}
	},
	goHome: function() {
		menu.go({page:'home'});
	},
	goNow: function(){
		menu.go(menu.now);
	},
	refreshList: function(){
		listPage.listData = '';
		menu.go(menu.now);
	},
	mask: {
		showInProcess: false,		// true when a show is in process
		hideWhenShown: false,		// false, or a callback/true if we need to hide as soon as shown
		hideInProcess: false,
		show: function(fn) {
			// shows the page-changing mask
			if ($('oot-loadingMask') !== null) return false; // its already there!
			var currPage = Ext.get('ootPage-'+menu.now.page);
			var p = Ext.get(document.body).createChild({id:'oot-loadingMask', style:'position:absolute;z-index:10000;background:white;text-align:center;'});
			p.hide();
			p.setXY(currPage.getXY());
			p.setHeight(currPage.getHeight());
			p.setWidth(currPage.getWidth());
			p.update("<br /><br /><div id='oot-loadingMask-message'></div>");
			menu.mask.showInProcess = true;
			p.fadeIn({duration:0.3, callback:function(){
				if (fn !== undefined) {fn();}
				// have we been told to hide, in the meantime?
				menu.mask.showInProcess = false;
				if (menu.mask.hideWhenShown !== false) {
					menu.mask.hide(menu.mask.hideWhenShown);
					menu.mask.hideWhenShown = false;
				}
			}});
		},
		hide: function(fn) {
			// hides, and then does callback
			if ($('oot-loadingMask') === null) return false; // its not there!
			if (menu.mask.showInProcess) {
				// still showing, so hold on!
				menu.mask.hideWhenShown = fn || true;
				return true;
			}
			Ext.get('oot-loadingMask').hide({duration:0.3,remove:true,callback:function() {
				if (fn !== undefined && fn !== true) {fn();}
			}});
		},
		message: function(msg) {
			if ($('oot-loadingMask') === null) return false; // its not there!
			Ext.get('oot-loadingMask-message').update(msg);
		},
		messageLoading: function() {
			menu.mask.message('<img src="/lib/ext2/resources/images/default/grid/loading.gif" style="width:16px;height:16px;" align="absmiddle">&#160;Page loading...');
		}
	},
	showPage:function(page) {
		return true;	// ie this is deprecated for the time being
		// shows the page named
		Ext.get('ootPage-'+page).dhwShow(true);
	},
	findList: function(listNum) {
		// searches for listNum in menu.data
		// returns false, or type: 'owner', 'manager', 'child:childId' - child is a special case!
		var MD = menu.data;
		if (MD.myLists == undefined) {return false;}
		var MDL = MD.myLists;
		if (MDL.owner !== undefined && MDL.owner[listNum] !== undefined) {return 'owner';}
		if (Ext.type(MDL.children) == 'array') {
			var findChild = MDL.children.find(function(childO){
				return (childO.lists !== "" && childO.lists[listNum] !== undefined);
			});
			if (findChild !== undefined) {return 'child:' + findChild.id;}
		}
		if (MDL.manager !== undefined && MDL.manager[listNum] !== undefined) {return 'manager';}
		return false;
	},
	numLists: function(type) {
		// send 'owner', 'manager', or 'child:childId' to get
		// an object: {active: num, completed: num}
		if (type != 'owner' && type != 'manager' && type.substr(0,5) != 'child') {return false;}
		var MD = menu.data;
		if (MD.myLists == undefined) {return false;}
		var MDL = MD.myLists, childId;
		if (type.substr(0,5) == 'child') {
			childId = Number(type.substr(6));
			type = 'children';
		}
		if (MDL[type] === undefined || MDL[type] === "") {return false;}
		var r = {active:0, completed:0};
		// make o the object to search
		var o;
		if (type == 'children') {
			var o2 = MDL.children.find(function(childO){
				return (childO.id == childId);
			});
			if (o2 === undefined) {return false;}
			o = o2.lists;
		} else {
			o = MDL[type];
		}
		for (var list in o) {
			if (o[list].completed) {
				r.completed++;
			} else {
				r.active++;
			}
		}
		return r;
	},
	getChild: function(id){
		// gets the object of child info from menu.data
		if (menu.data == undefined || menu.data.myLists == undefined) {return false;}
		if (Ext.type(menu.data.myLists.children) != 'array') {return false;}
		return menu.data.myLists.children.find(function(childO){
			return (childO.id == id);
		});
	},
	history: {
		currentHash: '',
		timer: 0,
		changing: false,
		running: false,
		title:'',
		encode: {'myaccount':'MyAccount', 'listsiown':'MyLists', 'listdetails':'EditDetails',
			'listusers':'ListFriends', 'newlist2':'NewList', 'listitems':'List',
			'listsimanage':'Managed', 'myfriendslists':'Friends', 'items':'Buying-Bought',
			'managechildren':'ManageChildren','friendsfamily':'FriendsAndFamily'},
		decode: '',
		lastO:'',
		ExtHistoryIsAdding: false,
		// Ext.History is what I should be using, so lets try with that
		go: function(hsh) {
			// based on the hsh, go to the appropriate page
console.log('hash', hsh);			
			var MH = menu.history;
			MH.changing = true;
			hsh = hsh.split("/");
			var o = {
				page: hsh[0],
				action: hsh[1],
				data: hsh[2],
				view: hsh[3]
			};
			for (var f in o) {
				if (o[f] !== "") {
					o[f] = MH.decode[o[f]] || ((f != 'data') ? o[f].toLowerCase() : o[f]);
				}
			}
			if (typeof listPage !== 'undefined' && listPage.listData != undefined) {
				listPage.listData = "";
			} 
			MH.changing = false;
			menu.go(o);			
		},
		start: function(){
			// TODO sort out what happens when a non-logged-in user uses a hashed url
			// TODO sort out how home.login uses menu.now / menu.goNow
			var MH = menu.history;
			// if decode hasnt been created, make it.
			if (MH.decode == "") {
				// create a reverse lookup
				MH.decode = {};
				for (var f in MH.encode) {
					MH.decode[MH.encode[f]] = f;
				}
			}
			// get the page title
			if (MH.title == "") {
				MH.title = document.title;
			}
			Ext.History.init(function(){});

	        Ext.History.on('change', function(hsh){
				// is it an 'add' change?
				if (MH.changing) {return false;}
				if (hsh != MH.currentHash && hsh !== "") {
					MH.go(hsh);
				}							
	        });
			// check to see if any existing hash means we need to move
			var h = window.location.hash.replace(/#/g, "");
			if (h !== "") {
				MH.go(h);
			}
			MH.running = true;
		},
		hash: function(o){
			// ignore if its a redirect
			if (o.sender !== undefined && o.sender == "redirect") {
				return false;
			}
			var MH = menu.history, newF, newO={};
			// ignore if were not running
			if (!MH.running) {
				return false;
			}
			// dont do a copy
			if (MH.lastO == Object.toQueryString(o)) {
				return false;
			}
			// rest the lastO
			MH.lastO = Object.toQueryString(o);
			
			// create a new hash, encoding items as we go
			for (var f in o) {
				if (o[f] !== "") {
					if (f != 'data') {
						if (MH.encode[o[f]] == undefined) {
							newF = o[f].substr(0, 1).toUpperCase() + o[f].substr(1);
							MH.encode[o[f]] = newF;
							if (MH.decode != "") {
								MH.decode[newF] = o[f];
							}
						}
						newO[f] = MH.encode[o[f]];
					} else {
						newO[f] = o[f];
					}
				} else {
					newO[f] = "";
				}
			}
			var hsh = newO.page + "/" + newO.action + "/" + newO.data + "/" + newO.view;
			MH.changing = true;
			Ext.History.add(hsh, true);
			document.title = MH.title + " ~ " + hsh.replace(/\/+/gi, " ~ ").replace(/~\s$/, "");
			// change the iframe title in IE - this means the history gets the title
			if (Ext.isIE) {
				Ext.getDom(Ext.History.iframeId).contentWindow.document.title = document.title;
			}
//			window.location.hash = hsh;	// this is changed by Ext.History, and a double change makes a double click in IE
			MH.currentHash = hsh;
			MH.changing = false;			
		}
	} // eof menu.history
};
// eof MENU object ############################################################

var home = {
	pageStatus: false,
	show: function(o) {
		// draw the page, from what is there
		// always redraw - IE closes the E panel otherwise
		if ( true || home.pageStatus === false || o.action == 'refresh') {
			home.ready();
			home.pageStatus = true;
		}
		// reset the create email
		oot.createEmail = "";
//		menu.showPage('home');
		// if user is non-activated, show a warning
//		if (menu.data.myAccount !== undefined && menu.data.myLists === undefined && $('oot-account-not-activated')===null) {
		if (menu.data.myAccount !== undefined && menu.data.myLists === undefined) {
			Ext.Msg.alert("User Activation","Your account is not yet activated, please check 'My Account - View' for more information");
//			var p = Ext.get('oot-home-panel-c');
//			p.createChild({id:'oot-account-not-activated',cls:'error-text',html:"<br />Your account is not yet activated. Please check 'My Account' for more information."});
		} else if (menu.data.myLists !== undefined) {
/* deprecated
			if ($('oot-account-not-activated')!==null) {
				$('oot-account-not-activated').innerHTML = "";
			}
*/			
		}
	},
	logIn: function(o) {
		// when the login button is pressed
		// its complicated because it gets called from two different places
		// that send different values...
		// o could be the values of the form, or the clicked buttons object
		var myForm = o;
		if (o.login_password === undefined) {
			// called from the page form, not dialog
			if (!this.isValid()) {return false;}
			myForm = this.getValues();
		}

		if (!login.getChallenge()) {return false;}
		pword = login.encode(myForm.login_password,myForm.login_username);
		// lets do some AJAX stuff
		home.loginWaitBox = Ext.MessageBox.wait('Please Wait...', 'Checking your login');
		var pars = 'cmd=login&lac=login'
			+ '&lcd='+pword
			+ '&lun='+myForm.login_username
			+ '&lkp='+myForm.login_remember;
		oot.getData(pars, home.logInCheck);
	},

	createAccount: function(o) {
		// when buttons are pressed in the create account form
		// scope is the calling form
//		if (!this.isValid()) {return false;}
		if (this.isValid()) {
			var p = this.getValues();
			oot.createEmail = p.createEmail;
		}
		menu.go({page:'newaccount',action:'stage1'});
	},

	logInCheck: function(o) {
		// response to AJAX call
		if (o.responseText == "false") {
	        home.loginWaitBox.show({
				wait: false,
				msg: "We're sorry, but those log-in details were not recognised",
				title: "Login Error",
				buttons: Ext.MessageBox.OK
			});
		} else if (o.responseText == "user_not_activated"){
			// this response is currently de-activated server-side
			home.loginWaitBox.hide();
			home.checkActivation();	// this function does not exist
		} else {
			// login is successful
	        home.loginWaitBox.hide();			
			user.loggedIn = true;
			home.pageStatus = false;	// force a redraw of this page
			menu.data = Ext.decode(o.responseText);
			user.userName = menu.data.myAccount;
			menu.draw();
			// check to see if they should be forwarded to a listview
			if (typeof listPage !== 'undefined' && listPage.afterLogin !== "") {
				menu.go(listPage.afterLogin);
				listPage.afterLogin = "";
			}
			else {
				menu.go({
					page: 'myaccount'
				});
			}
		}
	},
	logOut: function(ask) {
		var ask = (ask === undefined) ? true : ask;
		var logOutFn = function(o) {
			if (o == 'yes') {
				document.mainLogout.submit();				
			}
		};
		if (ask) {
			Ext.MessageBox.confirm('Log Out', 'Are you sure you want to log out?', logOutFn);
		} else {
			logOutFn('yes');
		}
	},
	ready: function() {
		// does all the onReady items for the home.php page
		// from Ext2 this needs to be a viewport...
		// which weve already shown, so just make sure its there...
		
		Ext.getCmp('card-holder').layout.setActiveItem(0);
		Ext.getCmp('oot-home-panel-e').body.update("");		
		home.loginPanelForms();
		
	},
	loginPanelForms: function() {
		// draws the correct forms for the E panel on the home page
		// make the divs 
		var ePanel = Ext.getCmp('oot-home-panel-e').body;
		var b, createF;
		ePanel.update("");
		ePanel.createChild({id:'oot-home-form-login'});
		ePanel.createChild({id:'oot-home-form-viewlist'});		
		ePanel.createChild({id:'oot-home-form-create'});
		
		// make login / logout form, and create account
		if (user.loggedIn) {
			content.renderFromData({
				type:'form', id:'oot-home-form-login',
				config:{name:'logout', style:'padding:5px'},
				data:[{type:'fieldset', config:{legend:'Log Out'},
					fields:[
						{type: 'columns', items: [
							{type:'text',config:{width:155, html:'Click the Button...'}},
							{type:'button',config:{width:65, text:'Log Out', handler:home.logOut}}
						]}
					]
				}]
			});
		
			// can't create account
			content.renderFromData({
				type:'form', id:'oot-home-form-create',
				config:{name:'logout2', style:'padding:5px'},
				data:[{type:'fieldset', config:{legend:'Create Account'},
					fields:[
						{type:'text',config:{width:'220', html:'You need to Log Out before<br />you can create a new account.'}}
					]
				}]
			});

		} else {

			content.renderFromData({
				type:'form', id:'oot-home-form-login',
				config:{name:'login', labelWidth:75, labelAlign:'right', bodyStyle:'padding:5px'},
				fieldConfig:{login_username:{width:130}},
				data:[{type:'fieldset', config:{legend:'Log In'},
					fields:['login_username',
						{type: 'columns', items: [
						{type: 'column', config:{width:160}, fields:['login_password']},
						{type: 'text', config:{width:65, html:"<a title='Forgotten your password? Click here...' href='javascript:login.box.forgotten();'>Forgotten?</a>"}}
						]},
						{type: 'columns', items: [
						{type: 'column', config:{width:160}, fields:['login_remember']},
						{type: 'button', config:{width:55, text:'Log In', handler:home.logIn, type:'submit'}}
						]}
					]
				}]
			});

			// and the Create Account:
			oot.createEmail = "";	// needs resetting	
			content.renderFromData({
				type:'form', id:'oot-home-form-create',
				config:{name:'create_account', labelWidth:80, labelAlign:'left', style:'padding:5px'},
				data:[{type:'fieldset', config:{legend:'Create Account'},
					fields:['createEmail',
						{type: 'columns', items: [
						{type: 'text', config:{width:135, html:"<a title='Click here for more information about creating an account' href='javascript:void(menu.go({page:\"newaccount\"}))'>More Information</a>"}},
						{type: 'button', config:{width:85, text:'New Account', handler:home.createAccount}}
						]}
					]
				}]
			});						
		}
		// and the list form
		content.renderFromData({
			type:'form', id:'oot-home-form-viewlist',
			config:{name:'viewlist', labelWidth:50, labelAlign:'left', style:'padding:5px'},
			data:[{type:'fieldset', config:{legend:'View A List'},
				fields:[
					{type: 'columns', items: [				
					{type: 'column', config:{width:147}, fields:['view_list']},
					{type: 'button', config:{width:70, text:'View List', handler:viewList.enterListNum}}
					]}
				]
			}]
		});						
	}
};

var viewList = {
	// functions to allow a user to check a list, and apply to become a listuser
	// need to be here because they are global to the site
	dlg: "",
	msg: "",
	listNum:"",
	enterListNum: function(listNum) {
		// gives the user a dialog box:
		// 	to enter listNum [optional - detects if we have come from the home listview form, or have provided one as parameter]
		// 	and have a CaptchaCheck
		// listNum parameter could be the object that comes from a click, or a passed listNum
		
		var listNum = listNum || false;
		if (Ext.type(this) == 'object' && this.name === 'viewlist'){
			listNum = this.getValues().view_list;
		}
//		if (listNum && listNum !== "" && menu.now.page=='home') {
		if (listNum && listNum !== "") {
			// check to see if this is a list in our menu data - if it is, go straight there
			// this is laborious
		 	var md = menu.data;
			if (md.myAccount !== undefined) {
				// have we been invited to see it?
				var mInv = user.checkListInvitations();
				if (mInv) {
					// mInv is an array of invitation objects, with listNum set
					var thisInv = mInv.find(function(inv){
						return (inv['listNum'] == listNum);
					});
					if (thisInv !== undefined) {
						// yes, we have been invited
						user.viewInvitation(thisInv);
						return true;
					}
				}
				// what about in the menus...
				if (Ext.type(md.myLists)=='object') {
					if (Ext.type(md.myLists.owner) == 'object' && md.myLists.owner[listNum] !== undefined) {
						menu.go({page:'listsiown', action:'listitems', data:listNum}); return true;
					}
					if (Ext.type(md.myLists.manager) == 'object' && md.myLists.manager[listNum] !== undefined) {
						menu.go({page:'listsiown', action:'listitems', data:listNum}); return true;
					}
					if (Ext.type(md.myLists.children) == 'array') {
						if (md.myLists.children.find(function(childO){
							return (childO.lists !== "" && childO.lists[listNum] !== undefined);
						}) !== undefined) {
							menu.go({page:'listsiown', action:'listitems', data:listNum}); return true;
						}
/*						for(var ch in md.myLists.children) {
							if (Ext.type(md.myLists.children[ch] == 'object' && md.myLists.children[ch][listNum] !== undefined)) {
								menu.go({page:'listsiown', action:'listitems', data:listNum}); return true;
							}
						}
*/
					}
				}
				if (Ext.type(md.myFriendsLists) == 'object' && md.myFriendsLists[listNum] !== undefined) {
					menu.go({page:'listsiown', data:listNum}); return true;					
				}
			}
		}
		// make the form layout data first
		var fmData = [];
		if (!listNum) {
			fmData =[
				{type:'text', config:{style:'padding:5px',html:"Please enter the list number you'd like to view."}},
				'list_num'
			];
		}
		fmData = fmData.concat([
			{type:'text', config:{style:'padding:5px',html:"As a security measure, before you check this list we need to make sure that you're not a computer! Please enter the picture code."}},
			'captcha_attempt'
		]);
		
		// now make the dialog object
		var dfO = {
			type:'dialog',
			id:'oot-viewlist-get',
			config:{title:'View A List', width:320, height:240},
			keys:[27],buttons:['Submit','Cancel'],
			handler:viewList.checkList,
			content: {
				type:'form',
				config:{name:'checkList', labelWidth:95},
				data:fmData
			}
		};
		
		// and render the dialog
		viewList.dlg = content.renderDialog(dfO);
		// store the called listNum (or false)
		viewList.listNum = listNum;
	},
	checkList: function(o) {
		// handler for the captcha dialog, this is the dialog
		if (o.text == 'Cancel') {
			this.hide();
			this.destroy(true);
			menu.go({page:'myaccount'});
			return false;
		}
		if (o.text == 'Submit') {
			var f = this.ootContent;
			if (!f.getForm().isValid()) {return false;}
			var v = f.getForm().getValues();
			v.captcha_image = f.ootCaptchaImage;
			v.cmd='viewlist';
			if (v.list_num === undefined) {
				v.list_num = viewList.listNum;
			} else {
				v.list_num = v.list_num.toUpperCase();
				viewList.listNum = v.list_num;
			}

			this.hide();
			this.destroy(true);
			var msg = Ext.MessageBox.wait('Checking','Please Wait...');
			viewList.msg = msg;
			oot.getData(Ext.urlEncode(v), function(o){
				var r = Ext.decode(o.responseText);
				switch (r.result) {
					case 'captcha-wrong':
						msg.show({
							wait:false, msg:'Sorry, you got the picture code wrong.',
							buttons:Ext.MessageBox.OK, fn:function(){
								menu.goHome();
							}
						});
					break;
					case 'no-such-list':
						msg.show({
							wait:false, msg:'Sorry, that list does not exist.',
							buttons:Ext.MessageBox.OK, fn:function(){
								menu.goHome();
							}
						});
					break;
					case 'completed':
						msg.show({
							wait:false, msg:'Sorry, that list has been completed.',
							buttons:Ext.MessageBox.OK, fn:function(){
								menu.goHome();
							}
						});
					break;
					case 'success':
						if (r.data.already !== undefined) {
							// this user has already applied, already has their listuser status
							var mO = {wait:false, buttons:Ext.MessageBox.OK};
							var mm, ff;
							switch(r.data.already) {
								case '0':
									mm = "You have already applied to be a user of this list, and are waiting approval. Please contact the list manager for more information.";
									ff = "";
								break;
								case '1':
									mm = "You are already a list user for this list."
									ff = function(){menu.go({page:'myfriendslists',action:'listitems',data:viewList.listNum});};
								break;
								case '2':
									mm = "You have already applied to be a user of this list. Please contact the list manager for more information.";
									ff = "";
								break;
							}
							mO.msg = mm;
							if (ff !== "") {mO.fn = ff;}
							msg.show(mO);
						} else {
							viewList.showListInfo(r.data);							
						}
					break;
				}
			});
		}
	},
	showListInfo: function(listData) {
		// having Captcha-checked, we can show them some list info,
		// and invite them to create an account, and/or sign-up for the list
		viewList.listData = listData;
		
		var m = iconImage.list + "<b>List: " + viewList.listNum + "</b><br /><br />";
		m += "<b>" + listData.title + "</b> has " + listData.items + " item";
		if (Number(listData.items) != 1 ) {m += "s";}

		var mO = {wait:false, title:'View A List'};
		// are we logged in?
		if (menu.data.myAccount !== undefined) {
			// logged in
			m += "<br /><br />Would you like to apply to become a user of this list?";
			mO.msg = m;
			mO.buttons = Ext.MessageBox.YESNO;
			mO.fn = viewList.applyListUser;
			viewList.msg.show(mO);
		} else {
			// not logged in
			m += "<br /><br />To keep lists private, we ask that people have an account and log in, before they can see and use a friends list. "
				+ "Please make your choice: ";
			viewList.msg.hide();
			var msg = content.renderDialog({
				type:'dialog',
				id:'oot-viewlist-aclogin',
				config:{title:'View A List', width: 300, height:200, minHeight:100},
				keys:[27],buttons:['Create Account','Log In', 'Cancel'],
				handler:viewList.acLogin,
				content: {
					type:'form',
					config:{bodyStyle:'padding:5px',name:'aclogin'/*, width:270*/},
					data:[{type:'text', config:{/*width:270,*/ html:m}}]
				}				
			});
		}
	},
	applyListUser: function(o){
		// a logged in user would like to apply for list membership
		// o.text = Yes, No
		if (Ext.type(o) == 'object') {o = o.text;}
		switch(o) {
			case 'no':
				viewList.msg.hide();
				menu.go({page:'myaccount'});
			break;
			
			case 'yes':
				var m = "<b>Apply to become a List User</b>"
					+ "<br />In order to use a list,"
					+ " you have to be approved by its owner or manager. "
					+ "Fill in these details, and we'll contact them on your behalf. "
					+ "You'll get an email when they've made a decision."
					+ "<br /><br />List: <b>" + viewList.listNum
					+ "<br />" + viewList.listData.title
					+ "</b><br />Owned by: <b>" + viewList.listData.owner
					+ "<br />";
				viewList.msg.hide();
				viewList.dlg = content.renderDialog({
					type:'dialog',
					id:'oot-viewlist-apply',
					config:{title:'View A List', height:310, width:300, minHeight:200},
					keys:[27],buttons:['Apply', 'Cancel'],
					handler:viewList.applyListUser,
					content: {
						type:'form',
						config:{bodyStyle:'padding:5px',name:'aclogin', labelAlign:'top', labelWidth:200},
						data:[
							{type:'text', config:{width:270, html:m}},
							'known_to_owner_as', 'known_to_user_as'
						]
					}				
				});
			
			break;
			
			case 'Apply':
				var f = viewList.dlg.ootContent.getForm();
				if (!f.isValid()) {return false;}
				var pars = f.getValues();
				pars.cmd = "applylistuser";
				pars.list_num = viewList.listNum;
				Ext.get('oot-viewlist-apply-content').mask(oot.loadingIndicator + " Checking..");
				oot.getData(Ext.urlEncode(pars), function(o){
					var m = "";
					viewList.dlg.close();
					switch(o.responseText) {
						case 'success':
							m = "Your application to become a list user has been submitted."
								+ " You will be able to use the list when the owner / manager has approved it."
								+ " You will be notified by email of this.";
						break;
						case 'already':
							m = "You have already applied to be a list user for this list. Please contact the list manager personally for more information.";
						break;
						case 'fail':
							m = "There has been an error, and we have been unable to submit your application."
								+ "<br /><br />Please try again another time.";
						break;
					}
					Ext.MessageBox.alert('List User Application', m);
				});
				
			break;
			
			case  'Cancel':
				viewList.dlg.close();
			break;
		}
	},
	acLogin: function(o){
		// a non-logged in member has clicked a button
		// o.text = Create Account, Log In, Cancel
		this.hide();
		this.destroy(true);
		switch (o.text) {
			case 'Create Account':
				menu.go({page:'newaccount'});
			break;
			case 'Log In':
				menu.goHome();
			break;
			case 'Cancel':
			
			break;
		}

	}
}

var login = {
	
	challengePath: "/lib/login.challenge.php",
	lg_c: "",
	lg_s: "",
	secretForm: "LOGIN_data",
	
	dlg: "",
	box: {
		// allows login via a dialog box
		show: function() {
			if (login.dlg !== "") {
				return false;
			}
			login.dlg = content.renderDialog({
				type:'dialog',
				id:'oot-login-box',
				config:{title:'Log In', width:310, height:190},
				keys:[27],buttons:['Forgotten Password','New Account', 'Cancel'],
				handler:login.box.check,
				content: {
					type:'form',
					config:{bodyStyle:'padding:5px;',name:'login_dialog', labelWidth:80, labelAlign:'right'},
					fieldConfig:{
						login_username:{id:'oot-login-box-lu'},
						login_password:{id:'oot-login-box-lp'},
						login_remember:{id:'oot-login-box-re'}
					},
					data:[{type:'fieldset', config:{legend:'Log In'},
						fields:['login_username', 'login_password',
							{type: 'columns', items:[
								{type: 'column', config:{width:160}, fields:['login_remember']},
								{type: 'button', config:{width:55, text:'Log In', handler:login.box.logIn, type:'submit'}}
							]}
						]
					}]
				}
			});
		},
		killDlg: function(f){
			login.dlg.hide(null,function(){
				login.dlg.destroy();
				login.dlg = "";
				if (f !== undefined) {
					f();
				}
			});
		},
		logIn: function(){
			if (!this.isValid()) {return false;}
			var o = this.getValues();
			login.box.killDlg();	// destroys 'this'
			home.logIn(o);
		},
		check: function(o){
			if (Ext.type(o) !== 'string') {o = o.text}
			switch(o) {
				case 'Cancel':
					login.box.killDlg();
					if (typeof listPage !== 'undefined' && listPage.afterLogin !== "") {
						menu.goHome();
						listPage.afterLogin = "";
					}
				break;
				
				case 'Forgotten Password':
					login.box.killDlg(login.box.forgotten);
				break;
				
				case 'New Account':
					login.box.killDlg();
					menu.go({'page':'newaccount'});
				break;
				
				case 'Submit':
					// this actually comes from the Forgotten Password form
					var f = this.ootContent.getForm();
					if (!f.isValid()) {return false;}
					var m = Ext.get('oot-login-forgotten').mask(oot.loadingIndicator + " Checking...");
					var v = f.getValues();
					v.captcha_image = this.ootContent.ootCaptchaImage;
					v.cmd = 'forgotten';
					oot.getData(Ext.urlEncode(v), function(o){
						// response from 'forgotten'
						switch(o.responseText) {
							case 'success':
								login.box.killDlg();
								Ext.MessageBox.alert('Forgotten Password', 'You have been emailed a new password.');
							break;
							case 'captcha_wrong':
								login.box.killDlg();							
								Ext.MessageBox.alert('Forgotten Password', 'You got the picture code wrong.',function(){
									login.box.forgotten();	//let them do it again
								});
							break;
							case 'not_known':
								login.box.killDlg();
								Ext.MessageBox.show({
									buttons:{ok:'Create Account', cancel:'Cancel'},
									title:'User Not Known',
									msg:'Sorry, we have no user with that username.',
									fn: login.box.check
								})
							break;
							default:
								login.box.killDlg();
								Ext.MessageBox.alert('Forgotten Password', 'Sorry, there was an error, please try again later.');
							break;
						}
					});
				break;
				
				case 'ok':
					// is the 'Create Account' from above
					menu.go({page:'newaccount'});
				break;
			}			
		},
		forgotten: function(){
			// forgotten password
			login.dlg = content.renderDialog({
				type:'dialog',
				id:'oot-login-forgotten',
				config:{title:'Forgotten Password', width:310, height:200},
				keys:[27],buttons:['Submit','Cancel'],
				handler:login.box.check,
				content: {
					type:'form',
					config:{bodyStyle:'padding:5px;',name:'login_forgotten', labelWidth:100, labelAlign:'left'},
					fieldConfig:{
						login_username:{id:'oot-login-forgotten-lu'}
					},
					data:[
						{type:'text', config:{width:260, html:"Fill in this form, and we'll send you a new password."}},
						'login_username', 'captcha_attempt'
					]
				}
			});
		}
	},
	
	process: function(myForm){
		var f = myForm;
		var u = f.username.value;
		var p = f.password.value;		
		var k = f.keep.checked;
		var formData = {
			'id': f.id,
			'fields': {
				'username': {type:'email',name:'Email Address',def: "Enter your email here"},
				'password': {type:'password',name:'Password'}
				}
			};
		var result = DHW.formCheck(formData);
		if (result === false) {return false;}

		// it's OK, let's carry on
		if (!login.getChallenge()) {return false;}

		pword = login.encode(p,u);
		// change forms...
		f = document[login.secretForm];
		f.lcd.value = pword;
		f.lun.value = u;
		f.lac.value = 'login';
		f.lkp.checked = k;		
		f.submit();		
	},
	forgottenPassword: function(myForm) {
		// if the username is there, send a forgotten password code, 
		// otherwise request the username so they can try again
		// NOPE - can't use this scheme, we need a captcha check.
		var f = myForm;
		var u = f.username.value;
		var cp = f.captcha_attempt.value;
		var formData = {
			'id': f.id,
			'fields': {
				'username': {type:'email',name:'Email Address',def: "Enter your email here"},
				'captcha_attempt': {type:'text',name:'Picture Code'}
				}
			};
		var result = DHW.formCheck(formData);
		if (result === false) {return false;}

		// OK, submit the details
		f.submit();		
	},
	encode: function(pword, uname) {
		return hex_hmac_md5(login.lg_c,login.encode2(pword,uname));
	},
	encode2: function(pword,uname) {
		return hex_hmac_md5(uname+login.lg_s,pword);
	},
	getChallenge: function() {
		var result = false;
		var x = (window.ActiveXObject) ? new ActiveXObject("Microsoft.XMLHTTP"): new XMLHttpRequest();
			if (x) {
				x.open("GET", login.challengePath, false);
				x.send(null);
				if (x.status == 200) {
					var data = x.responseText;
					data = data.split("|");
					login.lg_c = data[0];
					login.lg_s = data[1];
					result = true;
					}
				}
		if (!result) {alert("Sorry, we are unable to access the server at this time.\nError Code: PC01");}
	    return result;
	}
};



// deprecated...
/*
var showList_old = {
	// all we need for showing a list
	bsCls: 'oot-listitem-', // base Class
	storeSetup: [
		{name: 'spacer1'},
		{name: 'item_id'},
		{name: 'sort_order'},
		{name: 'sort_order_owner'},
		{name: 'description_text'},
		{name: 'price', type:'float'},
		{name: 'vetting_status'},
		{name: 'spacer2'}
	],
	baseColModel: [
			{header:"", width:10, sortable:false, fixed:true, resizeable:false, dataIndex:'spacer1'},
			{header:"", sortable:false, fixed:true, resizeable:false, dataIndex:'item_id', hidden:true},			
			{header:"Pref", width:40, sortable:true, dataIndex:'sort_order', align:'center'},
			{id: 'ootlist-changecol-', header:"Gift Description", width:250, sortable:true, dataIndex:'description_text'},
			{header:"Price", width:70, sortable:true, renderer:oot.renderPrice, dataIndex:'price', align:'right'},
			{header:"", width:10, sortable:false, fixed: true, resizeable:false,  dataIndex:'spacer2'}
	],
	
	auto: function() {
		// gets the list data, and creates appropriate grids
		if ($('oot-fulllistdata') === null) {return false;}
		var thisList = DHW.getJSON('oot-fulllistdata');
		// what kind of data ? all / three-cats
		var cats = (thisList.all !== undefined) ? ['all'] : ['available', 'pencilled', 'bought'];
		var catTitle = {
			all: 'All Items',
			available:'These items are Available:',
			pencilled:'These items Are Pencilled-In for purchase:',
			bought:'These items have been Bought:'
			};
		// make a base Id
		var baseId = 'oot-fulllistdata-';
		// get the div
		var listDiv = Ext.get('oot-fulllistdata');
		listDiv.update("");	// blank the div
		// make the grids
		cats.each( function(c) {
			if (thisList[c] !== undefined && thisList[c] !== "") {
				// make a new div inside
				listDiv.createChild({tag:'h3', html:catTitle[c]});
				listDiv.createChild({tag:'div', id: baseId+c});
				// alter the data
				listPage.create(baseId+c, thisList[c], c);
			}
		});
	},
	create: function(divId, listData, listCat) {
		// create a list grid of category listCat using listData in divId;
		// turn the listdata into something useful - should we use JSON reader?
		var gridData=[], rowA, itemData, itemId;
		for (itemId in listData) {
			rowA = [];
			listData[itemId].item_id = itemId;
			listPage.storeSetup.each( function(s) {
				if (listData[itemId][s.name] !== undefined) {
					rowA.push(listData[itemId][s.name]);
				} else {
					rowA.push("");
				}
			});
			gridData.push(rowA);
		}
		var store = new Ext.data.Store({
	        proxy: new Ext.data.MemoryProxy(gridData),
	        reader: new Ext.data.ArrayReader({}, listPage.storeSetup)
			});
		store.setDefaultSort('sort_order', 'ASC');
		store.load();

		// create a colModel that fits the data
		var newColModel = [], cols = {};
		// get an array of col keys
		for (itemId in listData) {
			for (var c in listData[itemId]) {
				cols[c] = 1;
			}
			break; // we only need to check one record
		}
		listPage.baseColModel.each( function(rowO) {
			if (cols[rowO.dataIndex] !== undefined || rowO.header === "") {	// the spacers have header = ""
				var rowO2 = Object.clone(rowO);
				if (rowO2.id !== undefined) {rowO2.id += divId;}
				newColModel.push(rowO2);
			}
		});
		var colModel = new Ext.grid.ColumnModel(newColModel);
        var grid = new Ext.grid.Grid(divId, {
            ds: store,
            cm: colModel,
			enableColumnHide: false,
			enableColumnMove: false,
            autoExpandColumn: 'ootlist-changecol-'+divId		// the description_text column is always there!
        });
		grid.on('rowclick', listPage.clickRow);
		grid.render();		
	}	
};
*/
var content = {
	// handles the auto-display of content from php-generated data
	/* object format, in div class='oot-content'
	 * type:
	 * id: 
	 * config:{main config items }
	 * fieldConfig: {if there are a variety of fields - fieldname:{extra / override config items appropriate for its Ext.form.fieldtype}
	 * data:{see renderType functions for data format}
	 */
	// properties ###################
	store: {},	// holds all the Objects created, by id
	tips: {},	// stores quicktips, by help-image id
	d: {	// short for data
		/* there are two kinds of data here
		 * 'grid' contains default settings for all the different fields - the name is accidental
		 * other root keys are for specific kinds of content, and contain:
		 * 	gridConfig: if its a grid!
		 * 		expand:	the name of the column to expand if its a grid
		 * 		sort: an object to describe the sorting of a grid: {column:name, order: 'ASC' or 'DESC'}
		 * 		columns: [array of column names, in order]
		 * 		storeFields: [array of fields in the store]
		 * 			- 1. original: order mattered because we used ArrayReader
		 * 			- 2. new: order irrelevant, going over to JsonReader
		 * 		handlers: [array of handler objects: {event:eventName, h:handlerFunction}
		 * or
		 * 	standard: if its a form
		 * 		default config options from Ext.form.Form config
		 */	
		listSummary: {
			gridConfig: {
				json: {id:'list_num',root:'json'},				
//				expand: 'title',
				expand: '',
				sort: {column: 'completion', order: 'ASC'},
				columns: ['list_num','title','completion','bought','pencilled','left','verified','awaiting'],
				storeFields: ['list_num','title','completion','bought','pencilled','left','verified','awaiting'],
				selMod: {singleSelect:true},
				handlers: [{event:'rowclick', h:handlers.listSummaryClickRow}],
				filter:"",
				rowClass:"",
				grid: {
					autoHeight: true,
					width: 'auto'
				}
			}
		},
		form: {
			standard: {
				labelWidth: 110, id:'',name:'',
				buttonAlign:'right',
				labelAlign:'right',
				border: false
			}
		},
		listItems: {
			gridConfig: {
				json: {id:'item_id',root:'json'},
				expand: 'description_text',
//				expand: '',
				sort:{column:'sort_order', order: 'ASC'},
				columns: ['sort_order','description_text','price'],
				storeFields: ['item_id', 'category','sort_order','description_text','price','_spacer', 'status', 'sort_order_owner', 'mine', 'vetting_status', 'ic'],
				selMod: {singleSelect:true},
				grid: {
					autoHeight: true,
					width: 'auto'
				},
				handlers: "",
				filter:"",
//				rowClass:""
				rowClass: function(rec, indx) {
					if (rec.get('vetting_status') !== undefined && rec.get('vetting_status')!== '1') {
						return 'oot-rowapprove-' + rec.get('vetting_status');
					} else {
						return 'oot-rowcolour-' + rec.get('status');
					}
				}
			}
		},
		listUsers: {
			gridConfig: {
				json: {id:'user_id',root:'json'},				
				expand: 'known_to_owner_name',
				sort:{column:'lastname', order: 'ASC'},
				columns: ['known_to_owner_name','username','listuser_status','known_to_user_as','known_to_owner_as'],
				storeFields: ['user_id','fullname','username','firstname','lastname','listuser_status','known_to_user_as','known_to_owner_as'],
				selMod: {singleSelect:true},
				grid: {
					autoHeight: true,
					width: 'auto'
				},
				handlers: "",
				filter:"",
				rowClass:""
			}
		},
		listInvites: {
			gridConfig: {
				json: {id:'invite_id',root:'json'},				
				expand: 'known_to_owner_name',
				sort:{column:'invite_date', order: 'ASC'},
				columns: ['known_to_owner_name','invite_email','known_to_user_as','invite_date'],
				storeFields: ['invite_id','invite_email','known_to_user_as','known_to_owner_as','invite_date'],
				selMod: {singleSelect:true},
				grid: {
					autoHeight: true,
					width: 'auto'
				},
				handlers: "",
				filter:"",
				rowClass:""
			}
		},
		friends: {
			gridConfig: {
				json: {id:'friend_id',root:'json'},				
				expand: 'friend_name',
				sort:{column:'friend_name', order: 'ASC'},
				columns: ['friend_name','friend_email','known_to_friend_as','friend_select','friend_edit'],
				storeFields: ['friend_id','friend_email','known_to_friend_as','friend_name'],
				selMod: {singleSelect:true},
				grid: {
					autoHeight: true,
					width: 'auto'
				},
				handlers: "",
				filter:"",
				rowClass:""
			}
		},
		standardGrid: {
			// using this pretty empty config, you can make a grid for anything, by overriding config options.
			gridConfig: {
				json: {id:'',root:'json'},				
				expand: '',
				sort:'',
				columns: [],
				storeFields: [],
				selMod: {singleSelect:true},
				grid:{
					autoHeight: true,
					width: 'auto'
				},
				handlers: "",
				filter:"",
				rowClass:""
			}
		},
		dialog: {
			height:300,width:300,minHeight:200,minWidth:200,modal:true,proxyDrag: true,
			shadow: true,title:''
		}
	},
	
	// methods ######################
	auto: function(divId) {
		// scans div for class=oot-content items, takes their data and renders appropriately
		var divs = Ext.get(divId).select('div.oot-content',true);
		divs.each(content.render);
	},
	
	render: function(divEl) {
		// renders the oot-content in the Ext Element divEl
		// get the JSON array
		var data = Ext.decode(divEl.dom.innerHTML); // or DHW.getJSON(divId)
		// rename the data div, and create a sibling div? [tick]
		divEl.set({id:data.id+'-ootContent'});
		divEl.dhwHide();	// just in case
		data.div = divEl.insertSibling({id:data.id, html:oot.loadingIndicator+" Loading..."}, 'after');
		content.renderType[data.type](data);
	},
	renderFromData: function(data) {
		// creates content in pre-existing div data.id, from data object data, format as usual
		// no data div is created, so this can not be updated automatically
		if (data.addTo == undefined) {
			data.div = Ext.get(data.id);
		}
		return content.renderType[data.type](data);
	},
	renderDialog: function(data){
		// render a Dialog using data - have to do this separately to create a div for the dialog
		data.div = Ext.get(document.body).createChild({id:data.id});
		return content.renderType.dialog(data);
	},
	update: function(divId, pars, newContent, AJAXreturn) {
		/* updates the content in div with id divId
		 * pars: if specified, the original data is replaced with data from an AJAX call
		 * 		with these parameters
		 * newContent: if true (default: false) a new content-item will be created 
		 * 		as a child of divId,
		 * 		with data from AJAX call
		 * 		nb: if -ootContent does not exist, newContent becomes true
		 * AJAXreturn: used by the function to signal a return from AJAX
		 */	
		if ($(divId) === null) {return false;}
		pars = pars || "";
		newContent = newContent || false;

		// check we have got a data div if we should have one
		if ($(divId+'-ootContent')===null && !newContent) {
			// theres no data - we must be creating new content
			if (pars === "") {return false;} // no pars to get data
			newContent = true;
		}
		// get AJAX data if we need to
		if (pars !== "" && AJAXreturn === undefined) {
			oot.getData(pars, function(o) {
				content.update(divId, pars, newContent, o.responseText);
			});
			return true;
		}
		// get data about content
		var data = (AJAXreturn === undefined) ? DHW.getJSON(divId+'-ootContent'): Ext.decode(AJAXreturn);

		// create Div if we need to
		if (newContent) {
			if (AJAXreturn === undefined) {return false;} // should not happen
			// make a div for data
			Ext.get(divId).createChild({id:data.id+'-ootContent', cls:'oot-content'});
			// make an empty div for content
			Ext.get(divId).createChild({id:data.id});
			// rename divId
			divId = data.id;
		}
		if (AJAXreturn !== undefined) {
			// we have got new data, stick it in the ootContent div
			Ext.get(divId+'-ootContent').update(AJAXreturn);
		}
		data.div = Ext.get(divId);
		content.renderType[data.type](data);
	},
	
	renderType: {
		// methods for specific types
		// data contains: type / id / config /fieldConfig /  data / div
		
		listSummary: function(data) {
			content.renderType.gridMaker(data, content.d.listSummary);			
		},
		form: function(data){
			/* Ext2 version
			 * config: has config items for the Ext.FormPanel config
			 * data: is an array of (nested) items, which describes the form, in order
			 * 		each item can be:
			 * 		{type:fieldset / column / button, name:, id:, width:,config:{any config options},fields:[more nested items]}, or
			 * 			nb: button, config is {text: handler:}
			 * 		name_of_field,
			 * fieldConfig:
			 * 		add extra config options for fields
			 * 		in particular, a ComboBox can have store:{store config object}, and it gets converted
			 * 		eg: {manager_username:{store:{fields:['username'],data:[[a@b.com],[c@d.com]]}}}
			 * buttons: [array of {config: string/object button config, h:handler}s; form will be scope for handler]
			 * focus: a name a of a field to focus
			 */
			data.fieldConfig = data.fieldConfig || {};
			var fO = Object.clone(content.d.form.standard);
			if (Ext.type(data.config) == 'object') {
				for (var a in data.config) {
					fO[a] = data.config[a];
				}
			}
			data.div.update("");	// needs emptying
			// now construct an items array, to fill FormPanel
			content.ootButtons = false;	// reset / create some holders
			fO.items = content.renderType.formArray(data.data, data.fieldConfig, data.id);
			fO.renderTo = data.div.id;

			// add buttons - form buttons first
			if (Ext.type(data.buttons) == 'array') {
				fO.buttons = data.buttons.collect(function(o){
					if (o.h !== undefined) {
						o.config.handler = o.h;
					};
					return o.config;
				});
			}

			// render the form
			var f = new Ext.FormPanel(fO);

			// now render the extras - inline buttons need scope giving to them
			if (content.ootButtons) {
				content.ootButtons.each(function(but){
					// give the button scope: the form within the form panel
					but.scope = f.getForm();
				});
			}
			// now render Captcha Image, if there is one
			content.updateCaptcha(f, data.id);
			/*
			if ($(data.id+'-captcha_attempt')!== null) {
				Ext.get(data.id+'-captcha_attempt').insertSibling({id:data.id+'-ciHolder'});
				oot.getData('cmd=captchaImage', function(o){
					var d = Ext.get(data.id+'-ciHolder');
					d.update("");
					d.createChild({tag:'img',id:data.id+'-captchaImage', src:o.responseText, alt:'A Picture Code, to make sure you are a human!'});
					f.ootCaptchaImage = o.responseText;
				}, data.id+'-ciHolder');
			}
			*/
			// now sort out the help
			var i = Ext.get(data.div.id).select("img.oot-help-image");
			i.each(function(el){
				Ext.QuickTips.register({
					target: el,
					trackMouse: true,
					text: content.tips[el.dom.id]['text'],
					title: '<b>'+content.tips[el.dom.id]['title']+'</b>',
					showDelay: 50
				});			
			});
			// focus a field
			if (data.focus !== undefined && data.focus !== "") {
				f.getForm().findField(data.focus).getEl().focus();
//				var focId = f.getForm().findField(data.focus).id;
//				Ext.get(focId).focus();
			}
			data.obj = f;
			content.store[data.div.id] = data;
			return f;
		},
		formArray: function(itemListA, fieldConfig, formId) {
			// helper for form above, returns array of items from itemListA
			// extra config items for fields are in fieldConfig
			//		*** if you include a 'store', it creates a simple data store, from the object passed
			// can be called recursively
			var items = [];
			items = itemListA.collect( function(item) {
				var fieldO = {};
				// item can be fieldname, or object with parameter type
				if (item.type === undefined) {
					// item isnt an object, it should be a fieldname
					var gridData = fields[item];
					var fieldType = gridData.formFieldType.toLowerCase();
					fieldO = Object.clone(gridData.formFieldObject);
					if (Ext.type(fieldConfig[item]) == 'object') {
						for (var fd in fieldConfig[item]) {
							if (fd == 'store') {
								fieldO.store = new Ext.data.SimpleStore(fieldConfig[item][fd]);
							} else {fieldO[fd] = fieldConfig[item][fd];}
						}
					}
					// do the help image and text
					var tip = fieldO.tip || (gridData.tip || "");
					if (tip !== "") {
						content.tips[formId + "-help-" + item] = {text:tip, title:fieldO.fieldLabel};
						fieldO.fieldLabel += "<img src='" + oot.helpImagePath + "'"
							+ " id='" + formId + "-help-" + item + "'"
							+ " class='oot-help-image'"
							+ " alt='Hover here for Help'>";
					}
					if (fieldType == 'captcha') {
						// for a captcha, we add an id to a textField, 
						// then insert an image later when the form renders,
						// with the assumption that theres only one captcha
						fieldO.id = formId+"-captcha_attempt";
						fieldO.xtype = 'textfield'
					} else {
						fieldO.xtype = fieldType;
					}
					return fieldO;
				} else {
					// 'item' is an object - could be fieldset, column, or button
					var fsO = {};
					if (item.id !== undefined) {fsO.id = item.id;}
					if (item.legend !== undefined) {fsO.legend = item.legend;}
					if (item.width !== undefined) {fsO.width = item.width;}
					if (Ext.type(item.config) == 'object') {
						for (var ic in item.config) {fsO[ic] = item.config[ic];}
					}
					switch (item.type) {
						case 'columns':
							// this starts the nesting that columns need in Ext2
							fsO.layout = 'column';
							fsO.border = false;
							fsO.items = content.renderType.formArray(item.items, fieldConfig, formId);
						break;
						case 'column':
							fsO.layout = 'form';
							fsO.border = false;
							fsO.items = content.renderType.formArray(item.fields, fieldConfig, formId);
						break;
						case 'fieldset':
							fsO.xtype = 'fieldset';
							fsO.autoHeight = true;
							fsO.items = content.renderType.formArray(item.fields, fieldConfig, formId);
							fsO.title = fsO.legend;	// ext2 uses title instead of legend
						break;
						case 'button':
							// make a panel, render button straight into it
							fsO.border = false;
							fsO.items = new Ext.Button(item.config);
							// to set the scope, the form needs to be rendered, so we do that later
							content.ootButtons = content.ootButtons || [];
							content.ootButtons.push(fsO.items);
						break;
						case 'text':
							// make a panel with text in it - easy! (ext2 easy!)
							fsO.border = false;
						break;
						default:
							// this did eg, formO.fieldset(fsO), or column;
							// i think its now redundant in ext2
alert('2411 is not redundant: '+ item.type);
							fsO.layout = item.type;
							if (item.fields !== undefined) {
								fsO.items = content.renderType.formArray(item.fields, fieldConfig, formId);
							}
						break;
					}
					return fsO;
				}
			});
			return items;
			
		},
		
		
		
		

		listItems: function(data) {
			/* type: listItems, id: as usual
			 * config: {
			 *  expand: the name of the col to expand - defaults to description_text
			 * 	columns:[array of columns, to which spacers will be added before/after, default exists]
			 *  sort: {column: name of column to sort (default exists), order: 'ASC' or 'DEC'}
			 *  handlers: array of eg.[{event:'rowclick', h:handlers.listSummaryClickRow}]
			 *  filter: {key:name, value:string} [optional] filters the store by key=string
			 *  category: a name, so we can get the right colour spacers
			 * }
			 * data: [array of row-arrays, according to config.columns]
			 * fieldConfig: {fieldName:{override ColumnModel settings for fieldname}}
			 */
			return content.renderType.gridMaker(data, content.d.listItems, false);
		},
		listItemsEditor: function(data) {
			// an editor grid version
			return content.renderType.gridMaker(data, content.d.listItems, false, true);			
		},
		listUsers: function(data){
			// config as for listItems
			return content.renderType.gridMaker(data, content.d.listUsers, false);
		},
		listInvites: function(data) {
			return content.renderType.gridMaker(data, content.d.listInvites, false);
		},
		friends: function(data) {
			return content.renderType.gridMaker(data, content.d.friends, false);
		},
		standardGrid: function(data) {
			// use this to make any grid - use the config object to override things
			/* ie call renderFromData({
			  		id: some_id,
			  		type: 'standardGrid',
			  		data: {json:[{lots of json datas},{},{}]},
			  		config: {
			  			json: {id:'the_id_of_the_id_field',root:'json'},
			  			expand: 'name_of_expand_column',
			  			sort:{column:'field_to_sort_by', order:'ASC'},
			  			columns: [array of names],
			  			storeFields: [array of names],
			  			handlers: array of eg.[{event:'rowclick', h:handlers.listSummaryClickRow}],
			  			rowClass: a function if you want one
			  		},
			  		fieldConfig: {fieldName:{override ColumnModel settings for fieldname}}
			  })
			 */
			return content.renderType.gridMaker(data, content.d.standardGrid, false);
		},
		gridMaker: function(data, defaultObj, spacers, editGrid) {
			/* Makes Grids, using data as usual, and the defaultObj from this content class
			 * eg: defaultObj = content.d.listItems
			 * spacers: boolean - add spacers before/after row? 
			 * data can contain:
			 * type: listItems, id: as usual
			 * addTo: instead of rendering in data.div, you can add the gridPanel to the Ext.Component with this id
			 * config: overrides the gridConfig settings - see alse under content.d above{
			 *  expand: the name of the col to expand - defaults to description_text
			 * 	columns:[array of columns, to which spacers will be added before/after, default exists]
			 *  storeFields:[array of fields that the store info contains]
			 *  sort: {column: name of column to sort (default exists), order: 'ASC' or 'DEC'}
			 *  grid: object of Ext.grid.Grid config items
			 *  filter: {key:name, value:string} [optional] filters the store by key=string
			 *  rowClass: [optional] function to give name of row class from ( Record record, Number index ) - see Ext.grid.GridView.getRowClass
			 * }
			 * handlers: array of eg.[{event:'rowclick', h:handlers.listSummaryClickRow}]
			 * data: [array of json rows, according to config.columns]
			 * fieldConfig: {fieldName:{override ColumnModel settings for fieldname}}
			 */
			if (data.addTo === undefined) {
				Ext.get(data.div.id).update("");
			}
			var gridConfig = Object.clone(defaultObj.gridConfig);
			var spacers = spacers || false;
			var editGrid = editGrid || false;
			// override config settings:
			if (Ext.type(data.config)== 'object') {
				for (var c in data.config) {
					if (gridConfig[c]!== undefined) {
						gridConfig[c] = data.config[c];
					}
				}
			}

			if (spacers) {
				// we have to add a column on either end of the models and data
				gridConfig.columns.unshift('spacerL');
				gridConfig.columns.push('spacerR');
/*				gridConfig.storeFields.unshift('spacer1');
				gridConfig.storeFields.push('spacer2');
				data.data = data.data.collect(function(row){
					row.unshift("");
					row.push("");
					return row;
				});
*/				
			}
			// create the data store:
			var dss = gridConfig.storeFields.collect( function(item){
				return Object.clone(fields[item].store);
			});
			var store = new Ext.data.Store({
				proxy: new Ext.data.MemoryProxy(data.data),
				reader: new Ext.data.JsonReader({id:gridConfig.json.id,root:gridConfig.json.root}, dss)
			});

			// create the colModel setup
			var cms = gridConfig.columns.collect( function(item){
				var o = Object.clone(fields[item].column);
				// override column settings from fieldConfig
				if (data.fieldConfig !== undefined && data.fieldConfig[item] !== undefined) {
					for (var ff in data.fieldConfig[item]) {
						o[ff] = data.fieldConfig[item][ff];
					}
				}
				// set the auto-expand col
//				if (gridConfig.expand == item) {o.id = data.div.id + '-expand';}
				if (gridConfig.expand == item) {o.id = data.id + '-expand';}
				return o;
			});
			// find out the grid width
			var gridWidth = 0;
			gridConfig.columns.each( function(item){
				if (fields[item].column.width !== undefined) {
					gridWidth += fields[item].column.width;
				}
			});
			// do some store stuff...
			store.setDefaultSort(gridConfig.sort.column, gridConfig.sort.order);
			store.load();
			if (gridConfig.filter !== "") {
				store.filter(gridConfig.filter.key, gridConfig.filter.value);
			}
			var colModel = new Ext.grid.ColumnModel(cms);
			var EGG = {				// stands for Ext.grid.Grid config
				ds: store,
				cm: colModel,
				selModel: new Ext.grid.RowSelectionModel(Object.clone(gridConfig.selMod))
			};
			if(Ext.type(gridConfig.grid)=='object') {
				for (var eggItem in gridConfig.grid) {
					EGG[eggItem] = gridConfig.grid[eggItem];
				}
			}
			// set the expand column, or the width
			if (gridConfig.expand != "") {
//				EGG.autoExpandColumn = data.div.id + '-expand';
				EGG.autoExpandColumn = data.id + '-expand';
			} else if (gridWidth > 0) {
				EGG.width = gridWidth;
			}
	        var grid = (editGrid)? new Ext.grid.EditorGridPanel(EGG) : new Ext.grid.GridPanel(EGG)
			if (Ext.type(gridConfig.handlers) == 'array') {
				gridConfig.handlers.each(function(hand){
					grid.on(hand.event, hand.h);
				});
			}
			data.obj = grid;
//			content.store[data.div.id] = data;
			content.store[data.id] = data;

			if (spacers) {
				grid.on('render', function() {
					// write the style info to style the left & right-column spacer
					var colrs = {all:'blue',available:'green',pencilled:'orange',bought:'red'}
					var rightCol = -1 + gridConfig.columns.length;
					var leftColCls = 'td.x-grid-td-0';
					var rightColCls = 'td.x-grid-td-'+rightCol;
					var leftCells = Ext.get(data.div.id).select(leftColCls,true);
					leftCells.each(function(cl){
						cl.setStyle('background','left no-repeat url(/images/listitem_'+colrs[spacers]+'2_lr.png)');
					});
					var rightCells = Ext.get(data.div.id).select(rightColCls,true);
					rightCells.each(function(cl){
						cl.setStyle('background','right no-repeat url(/images/listitem_'+colrs[spacers]+'2_lr.png)');
						return true;
						});
				});
			}

			if (gridConfig.rowClass !== "") {
				grid.getView().getRowClass = gridConfig.rowClass;
			}
			if (data.addTo == undefined) {
				grid.render(data.div.id);
			} else {
				Ext.getCmp(data.addTo).add(grid);
//				grid.render();
				oot.viewport.doLayout();
			}
			return grid;	
		},

		friendsLists: function(data) {
			
		},
		menu: function(data) {
			
		},
		button: function(data) {
			/* eg of object:
			{	type:'button',
				id:'ootbutton-myaccount-createnewlist',	// nb the id is sender-page-buttonId, for the handler
				data:{label:'Create a new list:', labelAlign:'right', labelWidth:200}, 
				config:{text:'New List',handler:handlers.button}}
			 */
			var d = data.div;
			d.update("");
			if (data.data !== undefined && data.data.label !== undefined) {
				var labelO = {
					tag:'span',
					id:data.id+"-label",
					cls:'oot-button-label',
					html:data.data.label
				};
				labelO.style = "text-align:" + ((data.data.labelAlign !== undefined) ? data.data.labelAlign : 'left') + ";";
				if (data.data.labelWidth !== undefined) {
					labelO.style += "width:"+data.data.labelWidth+"px;";
				}
				d.createChild(labelO);
			}
			d.createChild({
				tag:'span',
				id:data.id+"-button",
				cls:'oot-button-button'
			});
			data.config.renderTo = data.id + "-button";
			new Ext.Button(data.config);
		},
		dialog: function(data) {
			/* This shows a Window as a Dialog, with a form in it
			 * data object:
			 * 		id: id to use for the div, the content will get id-content
			 * 		config: override the default Ext.BasicDialog config settings, including 'title'
			 * 		keys: [array of keys to listen to] eg. [27] for ESC
			 * 		buttons: [array of buttons to add] eg. ['Save', 'Cancel']
			 * 		handler: all events get sent to the handler, with scope of the BasicDialog
			 * 			nb, this.ootContent will have the content object in it!
			 * 		content: a config object for any other content.?? as above
			 * 
			 */
			var boxConfig = Object.clone(content.d.dialog);
			// override the config settings
			for (var fd in data.config) {boxConfig[fd] = data.config[fd];}
			boxConfig.autoHeight = true;
			// add keys as a config item
			if (data.keys !== undefined) {
				boxConfig.keys = data.keys.collect(function(k){
					return {key: k,fn: data.handler};
				});
			}
			var dlg = new Ext.Window(boxConfig);
			// add buttons
			if (data.buttons !== undefined) {
				data.buttons.each(function(b){
					dlg.addButton({
						text: b,
						handler: data.handler,
						scope: dlg
					});
				});
			}
			dlg.render(document.body);
			// now we add a div in which we will create some content
			var contentId = data.id + "-content";
			dlg.body.createChild({id:contentId})
			// now render the content
			data.content.id = contentId;
			dlg.ootContent = content.renderFromData(data.content);
			dlg.show();
			data.obj = dlg;
			content.store[data.id] = data;
			return dlg;
		}
	},
	updateCaptcha: function(dlgData, rootId){
		if ($(rootId+'-captcha_attempt')!== null) {
			Ext.get(rootId+'-captcha_attempt').insertSibling({id:rootId+'-ciHolder'});
			oot.getData('cmd=captchaImage', function(o){
				var d = Ext.get(rootId+'-ciHolder');
				d.update("");
				d.createChild({tag:'img',id:rootId+'-captchaImage', src:o.responseText, alt:'A Picture Code, to make sure you are a human!'});
				dlgData.ootCaptchaImage = o.responseText;
			}, rootId+'-ciHolder');
		}
	},
	updateTips: function(divId,tips) {
		// updates the tips created by content.render in divId
		// tips is optional - any fields named their get these tips instead of the default
		if ($(divId)===null) {return false;}
		var tips = tips || {};
		var i = Ext.get(divId).select("img.oot-help-image");
		i.each(function(el){
			var fieldName = el.dom.id.split("-");
			fieldName = fieldName.pop();
			Ext.QuickTips.unregister(el.dom.id);
			Ext.QuickTips.register({
				target: el,
				trackMouse: true,
				text: (tips.fieldName === undefined) ? fields[fieldName].tip : tips.fieldName,
				title: '<b>'+fields[fieldName].formFieldObject.fieldLabel+'</b>',
				showDelay: 50
			});			
		});
	}
};

// Extend the Ext.form.Vtypes definitions #########################################
// Ive had to remove all the masks - they dont work in IE7


//ootEmail #########
Ext.form.VTypes.ootemail = function(v) {
	// ootEmail is a normal email, but with -name (1 to 8 characters) added at the end
	return /^([\w]+)(.[\w]+)*@([\w-]+\.){1,5}([A-Za-z]){2,4}(\-[A-Za-z]{1,8})?$/.test(v);
}
Ext.form.VTypes.ootemailText = "This field should be an email address, with possibly a child's name (up to 8 letters) added at the end";
//Ext.form.VTypes.ootemailMask = /[a-z0-9_\.\-@]/i;

// password #########
Ext.form.VTypes.password = function(v) {
	if ((v.length < 5)
		|| (v.search(/[^a-zA-Z0-9]/)!=-1)
		|| (v.search(/[a-zA-z]/) == -1)
		|| (v.search(/[0-9]/) == -1)) {
		return false;
	} else {
		return true;
	}
}
Ext.form.VTypes.passwordText = "The password must be at least 5 letters long,<br />containing at least one letter, at least one number.";
//Ext.form.VTypes.passwordMask = /[a-z0-9_]/i;

// num #########
Ext.form.VTypes.num = function(v) {
	return /^[0-9\.]+$/.test(v);
}
Ext.form.VTypes.numText = "This field should only contain numbers.";
//Ext.form.VTypes.numMask = /[0-9\.]/i;

// dateYMD #########
Ext.form.VTypes.dateymd = function(v) {
	return /^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$/.test(v);
}
Ext.form.VTypes.dateymdText = "This field should be a date in the format YYYY-MM-DD<br />e.g. 2007-12-25 means 25th Dec 2007";
//Ext.form.VTypes.dateymdMask = /[0-9\-]/i;

// dateDmY #########
Ext.form.VTypes.datedmy = function(v) {
	return /^[0-9]{1,2}\s[A-Za-z]{3}\s[0-9]{4}$/.test(v);
}
Ext.form.VTypes.datedmyText = "This field should be a date in the format d Mon YYYY<br />e.g. 25 Dec 2007";
//Ext.form.VTypes.datedmyMask = /[0-9A-Za-z\s]/i;

// text #############
Ext.form.VTypes.text = function(v) {
	return /^[a-zA-Z0-9_\"\'&\()!,\.\s\-?]+$/.test(v);
}
Ext.form.VTypes.textText = "This field can only contain letters, numbers, spaces and -\"'&()!,.";
//Ext.form.VTypes.textMask = /[a-z0-9_\"\'&\()!,\.\s\-]/i;

// justtext #############
// no numbers - a security precaution when entering names for 'bought' etc
Ext.form.VTypes.justtext = function(v) {
	return /^[a-zA-Z_\"\'&\()!,\.\s\-?]+$/.test(v);
}
Ext.form.VTypes.justtextText = "This field can only contain letters, spaces and -\"'&()?!,.";
//Ext.form.VTypes.justtextMask = /[a-z_\"\'&\()!,\.\s\-]/i;

// listNum #############
Ext.form.VTypes.listnum = function(v) {
	return /^[0-9A-Z]{4}\-[0-9A-Z]{4}$/.test(v);
}
Ext.form.VTypes.listnumText = "List Numbers are in the format NNNN-NNNN, using numbers and capital letters";
//Ext.form.VTypes.listnumMask = /[0-9A-Z\-]/;

// urlimage #############
Ext.form.VTypes.urlimage = function(v) {
//	return /^http(s?):\/\/([\-\w]+\.)+\w{2,4}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\/+]*)(\.(png)|(gif)|(jpg)))$/i.test(v);
	return /^https?:\/\/[-A-Za-z0-9+&@#\/%?=~_|!:,.;]+$/i.test(v);
"https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+"

}
Ext.form.VTypes.urlimageText = "This should be the URL of an image";
//Ext.form.VTypes.urlimageMask = /[\/:\w+]/;

/* I extend Element again to add ootHeader
 * 
 */
Ext.Element.prototype.ootHeading = function(text, hint, asTip) {
	var cnO, hint = hint || "", asTip = asTip || false;
	if (asTip) {
		cnO = {
			tag:'img',
			src: oot.helpImagePath,
			alt: hint,
			cls:'oot-help-image'
		};
	} else {
		cnO = {
			tag:'span',
			cls:'oot-hint',
			html:hint
		}
	}
	var h = this.createChild({
		tag:'h2',
		html:text,
		id:Ext.id("","oot-gen")
	});
	var hintEl = h.createChild(cnO);
	if (asTip) {
		Ext.QuickTips.register({
			target: hintEl,
			trackMouse: true,
			text: hint,
			title: '<b>'+text +'</b>',
			showDelay: 50
		});			
	}
	return h;
}
/* and also ootButtonBar
*/

Ext.Element.prototype.ootButtonBar2 = function(o) {
	// draws a button bar
	/* parameters:
	 * ANY Ext.button parameters, eg
	 * text: of the button
	 * qtip: qtip
	 * handler: when clicked
	 * ootCls: added to oot-buttonbar- for specific colourings etc
	 * width: string, either '200px' or '50%', or 'auto' (Eg)
	 */
	var f = {text:"", qtip:false, handler:false, ootCls:"red", width:"100%", cls:""};
	for (var ff in f) {
		o[ff] = o[ff] || f[ff];
	}
	// change the cls
	o.cls = 'oot-button oot-button-' + o.ootCls + ((o.cls !== "") ? " " + o.cls : "");
	// change qtip to tooltip
	if (o.qtip) {
		o.tooltip = o.qtip;
	}
	var d = this.createChild();
	// create the button
	var b = new Ext.Button(o);
	b.render(d);
	// now re-width the button
	b.getEl().setStyle('width',o.width)
	return b;
}
