<commit_before>/* **************************************************************

   Copyright 2011 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


************************************************************** */

/*
An extension for working within the Zoovy UI.


finder -
'path' refers to either a category safe id (.safe.name) or a list safe id ($mylistid)
'safePath' is used for a jquery friendly id. ex: .safe.name gets converted to _safe_name and $mylistid to mylistid).


NOTE
 - admin 'set' calls are hard coded to use the immutable Q so that a dispatch is not overridden.
 - in the calls, 'dispatch' was removed and only init is present IF we never check local storage for the data.
 
 
calls
 -> init should contain any code necessary for checking localStorage or, when supported, local DB.
 -> dispatch should contain everything needed for the dispatch, so that if it is executed instead of init, it works fine. 
*/


var admin = function() {
// theseTemplates is it's own var because it's loaded in multiple places.
// here, only the most commonly used templates should be loaded. These get pre-loaded. Otherwise, load the templates when they're needed or in a separate extension (ex: admin_orders)
	var theseTemplates = new Array('adminProdStdForList','adminProdSimpleForList','adminElasticResult','adminProductFinder','adminMultiPage','domainPanelTemplate','pageSetupTemplate'); 
	var r = {
		
	vars : {
		tab : null, //is set when switching between tabs. it outside 'state' because this doesn't get logged into local storage.
		tabs : ['setup','sites','jt','product','orders','crm','syndication','reports','utilities'],
		state : {},
		tab : 'home',
		templates : theseTemplates,
		willFetchMyOwnTemplates : true,
		"tags" : ['IS_FRESH','IS_NEEDREVIEW','IS_HASERRORS','IS_CONFIGABLE','IS_COLORFUL','IS_SIZEABLE','IS_OPENBOX','IS_PREORDER','IS_DISCONTINUED','IS_SPECIALORDER','IS_BESTSELLER','IS_SALE','IS_SHIPFREE','IS_NEWARRIVAL','IS_CLEARANCE','IS_REFURB','IS_USER1','IS_USER2','IS_USER3','IS_USER4','IS_USER5','IS_USER6','IS_USER7','IS_USER8','IS_USER9'],
		"dependencies" : ['store_prodlist','store_navcats','store_product','store_search'] //a list of other extensions (just the namespace) that are required for this one to load
		},



					////////////////////////////////////   CALLS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\		



	calls : {


		appResource : {
			init : function(filename)	{
				this.dispatch(filename);
				},
			dispatch : function(filename)	{
				app.model.addDispatchToQ({"_cmd":"appResource","filename":filename,"_tag" : {"datapointer":"adminImageFolderList"}});	
				}
			
			}, 

			/*
a typical 'fetchData' is done for a quick determination on whether or not ANY data for the category is local.
if not, we're obviously missing what's needed.  If some data is local, check for the presence of the attributes
requested and if even one isn't present, get all.
datapointer needs to be defined early in the process so that it can be used in the handlecallback, which happens in INIT.
obj.PATH = .cat.safe.id
*/
		appPageGet : {
			init : function(obj,tagObj,Q)	{
				obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
				obj['_tag'].datapointer = 'appPageGet|'+obj.PATH;  //no local storage of this. ### need to explore solutions.
				
				this.dispatch(obj,tagObj,Q);
				r = 1;
				return r;
				},
			dispatch : function(obj,tagObj,Q)	{
				obj['_cmd'] = "appPageGet";
				app.model.addDispatchToQ(obj,Q);
				}
			}, //appPageGet
			
		appPageSet : {
			init : function(obj,tagObj,Q)	{
				this.dispatch(obj,tagObj,Q);				
				},
			dispatch : function(obj,tagObj,Q)	{
				obj._tag = tagObj;
				obj._cmd = 'appPageSet';
				app.model.addDispatchToQ(obj,Q);
				}			
			},

//the appSettings 'domains' in the response are hard coded to app.domain...
//probably should avoid using the appSettings for now. rootcat is probably safe.
		appConfig : {
			init : function(tagObj,Q)	{
				this.dispatch(tagObj,Q);				
				},
			dispatch : function(tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"appConfig","_tag" : tagObj},Q);
				}			
			},

		adminDomainList : {
			init : function(tagObj,Q)	{
//			app.u.dump("BEGIN admin.calls.adminDomainList");
				tagObj = tagObj || {};
				tagObj.datapointer = "adminDomainList";
				if(app.model.fetchData(tagObj.datapointer) == false)	{
					r = 1;
					this.dispatch(tagObj,Q);
					}
				else	{
					app.u.handleCallback(tagObj);
					}
				},
			dispatch : function(tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"adminDomainList","_tag" : tagObj},Q);
				}			
			},



//obj requires panel and pid and sub.  sub can be LOAD or SAVE
			adminUIDomainPanelExecute : {
				init : function(obj,tagObj,Q)	{
					tagObj = tagObj || {};
//save and load 'should' always have the same data, so the datapointer is shared.
					tagObj.datapointer = "adminUIDomainPanelExecute|"+obj.domain+"|"+obj.verb;
					this.dispatch(obj,tagObj,Q);
					},
				dispatch : function(obj,tagObj,Q)	{
					obj['_cmd'] = "adminUIDomainPanelExecute";
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);	
					}
				}, //adminUIProductPanelList



		navcats : {
//app.ext.admin.calls.navcats.appCategoryDetailNoLocal.init(path,{},'immutable');
			appCategoryDetailNoLocal : {
				init : function(path,tagObj,Q)	{
					tagObj = typeof tagObj !== 'object' ? {} : tagObj;
					tagObj.datapointer = 'appCategoryDetail|'+path;
					this.dispatch(path,tagObj,Q);
					return 1;
					},
				dispatch : function(path,tagObj,Q)	{
					app.model.addDispatchToQ({"_cmd":"appCategoryDetail","safe":path,"detail":"fast","_tag" : tagObj},Q);	
					}
				}//appCategoryDetail
			
			}, //navcats

			
		customer : {

			adminCustomerGet : {
				init : function(CID,tagObj,Q)	{
//					app.u.dump("CID: "+CID);
					var r = 0;
//if datapointer is fixed (set within call) it needs to be added prior to executing handleCallback (which needs datapointer to be set).
					tagObj = tagObj || tagObj;
					tagObj.datapointer = "adminCustomerGet|"+CID;
					if(app.model.fetchData(tagObj.datapointer) == false)	{
						r = 1;
						this.dispatch(CID,tagObj,Q);
						}
					else	{
						app.u.handleCallback(tagObj);
						}
					return r;
					},
				dispatch : function(CID,tagObj,Q)	{
//					app.u.dump("CID: "+CID);
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminCustomerGet";
					obj["CID"] = CID;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				},

//no local storage of this call. only 1 in memory. Will expand when using session storage if deemed necessary.
		adminCustomerLookup : {
			init : function(email,tagObj,Q)	{
				tagObj = tagObj || tagObj;
				tagObj.datapointer = "adminCustomerLookup";
				this.dispatch(email,tagObj,Q);
				},
			dispatch : function(email,tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"adminCustomerLookup","email":email,"_tag" : tagObj});	
				}			
			},
			adminCustomerSet : {
				init : function(CID,setObj,tagObj)	{
					this.dispatch(CID,setObj,tagObj)
					return 1;
					},
				dispatch : function(CID,setObj,tagObj)	{
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminCustomerSet";
					obj["CID"] = CID;
					obj['%set'] = setObj;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,'immutable');
					}
				}
			},
			
		product : {
			appProductGetNoLocal : {
				init : function(pid,tagObj,Q)	{
					this.dispatch(pid,tagObj,Q)
					return 1;
					},
				dispatch : function(pid,tagObj,Q)	{
					var obj = {};
					tagObj = tagObj || {};
					tagObj["datapointer"] = "appProductGet|"+pid; 
					obj["_cmd"] = "appProductGet";
					obj["pid"] = pid;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				},//appProductGetNoLocal
//app.ext.admin.calls.product.adminProductUpdate.init(pid,attrObj,tagObj,Q)
			adminProductUpdate : {
				init : function(pid,attrObj,tagObj)	{
					this.dispatch(pid,attrObj,tagObj)
					return 1;
					},
				dispatch : function(pid,attrObj,tagObj)	{
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminProductUpdate";
					obj["pid"] = pid;
					obj['%attribs'] = attrObj;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,'immutable');
					}
				}
				
			}, //product
		finder : {
			
			adminNavcatProductInsert : {
				init : function(pid,position,path,tagObj)	{
					this.dispatch(pid,position,path,tagObj);
					return 1;
					},
				dispatch : function(pid,position,path,tagObj)	{
					var obj = {};
					obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
					obj['_cmd'] = "adminNavcatProductInsert";
					obj.pid = pid;
					obj.path = path;
					obj.position = position;
					obj['_tag'].datapointer = "adminNavcatProductInsert|"+path+"|"+pid;
					app.model.addDispatchToQ(obj,'immutable');	
					}
				}, //adminNavcatProductInsert
			
			adminNavcatProductDelete : {
				init : function(pid,path,tagObj)	{
					this.dispatch(pid,path,tagObj);
					},
				dispatch : function(pid,path,tagObj)	{
					var obj = {};
					obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
					obj['_cmd'] = "adminNavcatProductDelete";
					obj.pid = pid;
					obj.path = path;
					obj['_tag'].datapointer = "adminNavcatProductDelete|"+path+"|"+pid;
					app.model.addDispatchToQ(obj,'immutable');	
					}
				} //adminNavcatProductDelete
			
			}, //finder




//obj requires sub and sref.  sub can be LOAD or SAVE
//reload is also supported.
			adminUIBuilderPanelExecute : {
				init : function(obj,tagObj,Q)	{
					tagObj = tagObj || {};
					if(obj['sub'] == 'EDIT')	{
						tagObj.datapointer = "adminUIBuilderPanelExecute|edit";
						}
					else if(obj['sub'] == 'SAVE')	{
						tagObj.datapointer = "adminUIBuilderPanelExecute|save";
						}
					else	{
						//catch. some new verb or a format that doesn't require localStorage.
						}
					this.dispatch(obj,tagObj,Q);
					},
				dispatch : function(obj,tagObj,Q)	{
					obj['_cmd'] = "adminUIBuilderPanelExecute";
					obj['_SREF'] = sref;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				} //adminUIProductPanelList

		}, //calls




					////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\





	callbacks : {
//callbacks.init need to return either a true or a false, depending on whether or not the file will execute properly based on store account configuration. Use this for any config or dependencies that need to occur.
//the callback is auto-executed as part of the extensions loading process.
		init : {
			onSuccess : function()	{
//				app.u.dump('BEGIN app.ext.admin.init.onSuccess ');
				var r = true; //return false if extension can't load. (no permissions, wrong type of session, etc)
//app.u.dump("DEBUG - template url is changed for local testing. add: ");

app.model.fetchNLoadTemplates(app.vars.baseURL+'extensions/admin/templates.html',theseTemplates);

app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/styles.css','admin_styles']);
app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/legacy_compat.js']);

app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/legacy_compat.js']);



/* used for html editor. */
app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/style/jHtmlArea.ColorPickerMenu.css','jHtmlArea_ColorPickerMenu']);
app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/style/jHtmlArea.css','jHtmlArea']);
//note - the editor.css file that comes with jhtmlarea is NOT needed. just sets the page bgcolor to black.

// colorpicker isn't loaded until jhtmlarea is done to avoid a js error due to load order.
app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/scripts/jHtmlArea-0.7.5.min.js',function(){app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/scripts/jHtmlArea.ColorPickerMenu-0.7.0.min.js'])}]);




				return r;
				},
			onError : function(d)	{
//init error handling handled by controller.				
				}
			}, //init

		

//executed when the extension loads
		initExtension : {
			onSuccess : function()	{
//				app.u.dump('BEGIN app.ext.admin.initUserInterface.onSuccess ');
				var L = app.rq.length-1;
//load any remaining resources into the app.
				for(var i = L; i >= 0; i -= 1)	{
					app.u.handleResourceQ(app.rq[i]);
					app.rq.splice(i, 1); //remove once handled.
					}
				app.rq.push = app.u.handleResourceQ; //reassign push function to auto-add the resource.

if(app.u.getBrowserInfo().substr(0,4) == 'msie' && parseFloat(navigator.appVersion.split("MSIE")[1]) < 10)	{
	app.u.throwMessage("<p>In an effort to provide the best user experience for you and to also keep our development team sane, we've opted to optimize our user interface for webkit based browsers. These include; Safari, Chrome and FireFox. Each of these are free and provide a better experience, including more diagnostics for us to maintain our own app framework.<\/p><p><b>Our store apps support IE8+<\/b><\/p>");
	}

if(app.u.getParameterByName('debug'))	{
	$('button','#debugPanel').button();
	$('#debugPanel').show()
	$('.debugContent','#debugPanel').append("<div class='clearfix'>Model Version: "+app.model.version+" and release: "+app.vars.release+"</div>");
	$('body').css('padding-bottom',125);
	$('#jtSectionTab').show();
	}

//get list of domains and show chooser.
				var $domainChooser = $("<div \/>").attr({'id':'domainChooserDialog','title':'Choose a domain to work on'}).addClass('displayNone').appendTo('body');
				$domainChooser.dialog({
					'autoOpen':false,
					'modal':true,
					'width': '90%',
					'height': 500,
					'closeOnEscape': false,
					open: function(event, ui) {$(".ui-dialog-titlebar-close", $(this).parent()).hide();} //hide 'close' icon.
					});


//make sure all the links in the header use the proper syntax.
				$('.bindByAnchor','#mastHead').each(function(){
					// app.u.dump("BEGIN #mastHead rewriteLink");
					app.ext.admin.u.rewriteLink($(this));
					})
//if supported, update hash while navigating.
// see handleHashState function for what this is and how it works.
				if("onhashchange" in window)	{ // does the browser support the hashchange event?
//					app.u.dump("WOOT! browser supports hashchange");
					_ignoreHashChange = false; //global var. when hash is changed from JS, set to true. see handleHashState for more info on this.
					window.onhashchange = function () {
//						app.u.dump("Hash has changed.");
						app.ext.admin.u.handleHashState();
						}
					}
	
//create shortcuts. these are used in backward compatibility areas where brian loads the content.
				window.navigateTo = app.ext.admin.a.navigateTo;
				window.showUI = app.ext.admin.a.showUI;
				window.loadElement = app.ext.admin.a.loadElement;
				window.prodlistEditorUpdate = app.ext.admin.a.uiProdlistEditorUpdate;
				window.changeDomain = app.ext.admin.a.changeDomain;
				window.linkOffSite = app.ext.admin.u.linkOffSite;
				window.adminUIDomainPanelExecute = app.ext.admin.u.adminUIDomainPanelExecute;
				window._ignoreHashChange = false; // see handleHashState to see what this does.
				


//if user is logged in already (persistant login), take them directly to the UI. otherwise, have them log in.
//the code for handling the support login is in the thisisanadminsession function (looking at uri)
if(app.u.thisIsAnAdminSession())	{
	app.ext.admin.u.showHeader();
	}
else	{
	$('#appPreView').hide();
	$('#appLogin').show();
	}
				}
			},

		showDataHTML : {
			onSuccess : function(tagObj)	{
//				app.u.dump("SUCCESS!"); app.u.dump(tagObj);
				$(app.u.jqSelector('#',tagObj.targetID)).removeClass('loadingBG').hideLoading().html(app.data[tagObj.datapointer].html); //.wrap("<form id='bob'>");
				}
			}, //showDataHTML


		handleLogout : {
			onSuccess : function(tagObj)	{
				document.location = 'logout.html'
				}
			},
//in cases where the content needs to be reloaded after making an API call, but when a showUI directly won't do (because of sequencing, perhaps)
//For example, after new files are added to a ticket (comatability mode), this is executed on a ping to update the page behind the modal.
		showUI : {
			onSuccess : function(tagObj)	{
				if(tagObj && tagObj.path){showUI(tagObj.path)
					}
				else {
					app.u.throwGMessage("Warning! Invalid path specified in _rtag on admin.callbacks.showUI.onSuccess.");
					app.u.dump("admin.callbacks.showUI.onSuccess tagObj (_rtag)");
					app.u.dump(tagObj);
					}
				}
			}, //showUI
		showDomainConfig : {
			onSuccess : function(){
				app.ext.admin.u.domainConfig();
				}
			},

		showElementEditorHTML : {
			onSuccess : function(tagObj)	{
//				app.u.dump("SUCCESS!"); app.u.dump(tagObj);
				var $target = $(app.u.jqSelector('#',tagObj.targetID))
				$target.parent().find('.ui-dialog-title').text(app.data[tagObj.datapointer]['prompt']); //add title to dialog.
				var $form = $("<form \/>").attr('id','editorForm'); //id used in product edit mode.
				$form.submit(function(event){
					event.preventDefault(); //do not post form.
					app.ext.admin.u.uiSaveBuilderElement($form,app.data[tagObj.datapointer].id,{'callback':'handleElementSave','extension':'admin','targetID':'elementEditorMessaging'})
					app.model.dispatchThis();
					return false;
					}).append(app.data[tagObj.datapointer].html);
				$form.append("<div class='marginTop center'><input type='submit' class='ui-state-active' value='Save' \/><\/div>");
				$target.removeClass('loadingBG').html($form);
				}
			}, //showElementEditorHTML


//executed after a 'save' is pushed for a specific element while editing in the builder.
		handleElementSave : {
			
			onSuccess : function(tagObj)	{
//First, let the user know the changes are saved.
				var msg = app.u.successMsgObject("Thank you, your changes are saved.");
				msg['_rtag'] = tagObj; //pass in tagObj as well, as that contains info for parentID.
				app.u.throwMessage(msg);

				if(app.ext.admin.vars.tab)	{
					app.u.dump("GOT HERE! app.ext.admin.vars.tab: "+app.ext.admin.vars.tab);
					$(app.u.jqSelector('#',app.ext.admin.vars.tab+'Content')).empty().append(app.data[tagObj.datapointer].html)
					}			
				}
			}, //handleElementSave

		showHeader : {
			onSuccess : function(){
				app.ext.admin.u.showHeader();
				},
			onError : function(responseData){
				app.u.throwMessage(responseData);
//				if(responseData.errid == "100")	{
//					app.u.throwMessage("This is most typically due to your system clock not being set correctly. For security, it must be set to both the correct time and timezone.");
//					} //this is the clock issue.
				$('#preloadAndLoginContents').hideLoading();
				}
			}, //showHeader

		handleDomainChooser : {
			onSuccess : function(tagObj){
//				app.u.dump("BEGIN admin.callbacks.handleDomainChooser.onSuccess");
				var data = app.data[tagObj.datapointer]['@DOMAINS'];
				var $target = $(app.u.jqSelector('#',tagObj.targetID));
				var L = data.length;
				if(L)	{
					var $ul = $('#domainList'); //ul in modal.
	//modal has been opened on this visit.  Domain list still reloaded in case they've changed.
					if($ul.length)	{$ul.empty()} //user is changing domains.
	//first time modal has been viewed.
					else	{
						$ul = $("<ul \/>").attr('id','domainList');
						}
					
					for(var i = 0; i < L; i += 1)	{
						$("<li \/>").data(data[i]).addClass('lookLikeLink').addClass(data[i].id == app.vars.domain ? 'ui-selected' : '').append(data[i].id+" [prt: "+data[i].prt+"]").click(function(){
							app.ext.admin.a.changeDomain($(this).data('id'),$(this).data('prt'))
							$target.dialog('close');
							}).appendTo($ul);
						}
					$target.hideLoading().append($ul);
					}
				else	{
//user has no domains on file. What to do?
					}
				},
			onError: function(responseData)	{
				var $target = $(app.u.jqSelector('#',responseData._rtag.targetID));
				$target.hideLoading();
				responseData.persistant = true;
				app.u.throwMessage(responseData);
				$target.append("<P>Something has gone very wrong. We apologize, but we were unable to load your list of domains. A domain is required.</p>");
				$("<button \/>").attr('title','Close Window').text('Close Window').click(function(){$target.dialog('close')}).button().appendTo($target);
				}
			}, //handleDomainChooser

		handleElasticFinderResults : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.handleElasticFinderResults.onSuccess.");
				var L = app.data[tagObj.datapointer]['_count'];
				$('#resultsKeyword').html(L+" results <span id='resultsListItemCount'></span>:");
//				app.u.dump(" -> Number Results: "+L);
				$parent = $(app.u.jqSelector('#',tagObj.parentID)).empty().removeClass('loadingBG')
				if(L == 0)	{
					$parent.append("Your query returned zero results.");
					}
				else	{
					var pid;//recycled shortcut to product id.
					for(var i = 0; i < L; i += 1)	{
						pid = app.data[tagObj.datapointer].hits.hits[i]['_id'];
//						app.u.dump(" -> "+i+" pid: "+pid);
						$parent.append(app.renderFunctions.transmogrify({'id':pid,'pid':pid},'adminElasticResult',app.data[tagObj.datapointer].hits.hits[i]['_source']));
						}
					app.ext.admin.u.filterFinderResults();
					}
				}
			}, //handleElasticFinderResults

/*
REMINDER!!!
Now that we have three params, findertype, path and attrib, we don't need three callbacks.
merge these into one.
JT - 2012-11-21 (on vaca)
*/

//callback executed after the navcat data is retrieved. the u, does most of the work.
		addFinderToDom : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callback.addFinderToDom.success");
				app.ext.admin.u.addFinder(tagObj.targetID,'NAVCAT',app.data[tagObj.datapointer].id);
				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer].pretty); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addFinderToDom

//callback executed after the navcat data is retrieved. the u.addfinder does most of the work.
		addPageFinderToDom : {
			onSuccess : function(tagObj)	{
				//app.u.dump("BEGIN admin.callback.addPageFinderToDom.success");
				//app.u.dump("app.data[tagObj.datapointer]"); app.u.dump(app.data[tagObj.datapointer]);
				app.ext.admin.u.addFinder(tagObj.targetID,'PAGE',app.data[tagObj.datapointer]._rtag.path,app.data[tagObj.datapointer]._rtag.attrib);
//				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer].pretty); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addFinderToDom

//callback executed after the appProductGet data is retrieved for creating a finder, specific to editing an attribute of a product (related items, for example)
		addPIDFinderToDom : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callback.addPIDFinderToDom.success");
				app.ext.admin.u.addFinder(tagObj.targetID,'PRODUCT',tagObj.path,tagObj.datapointer.split('|')[1],$('#prodFinder').attr('data-attrib'));
				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer]['%attribs']['zoovy:prod_name']+" ("+app.renderFunctions.parseDataVar(tagObj.path)+")"); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addPIDFinderToDom
			
//when a finder for a product attribute is executed, this is the callback.
		pidFinderChangesSaved : {
			onSuccess : function(tagObj)	{
				app.u.dump("BEGIN admin.callbacks.pidFinderChangesSaved");
				$('#finderMessaging').prepend(app.u.formatMessage({'message':'Your changes have been saved.','htmlid':'finderRequestResponse','uiIcon':'check','timeoutFunction':"$('#finderRequestResponse').slideUp(1000);"}))
				app.ext.admin.u.changeFinderButtonsState('enable'); //make buttons clickable
				},
			onError : function(responseData)	{
				responseData.parentID = "finderMessaging";
				app.u.throwMessage(responseData);
				app.ext.admin.u.changeFinderButtonsState('enable');
				}
			
			}, //pidFinderChangesSaved
//when a finder for a category/list/etc is executed...

		finderChangesSaved : {
			onSuccess : function(tagObj)	{


app.u.dump("BEGIN admin.callbacks.finderChangesSaved");
var uCount = 0; //# of updates
var eCount = 0; //# of errros.
var eReport = ''; // a list of all the errors.

var $tmp;

$('#finderTargetList, #finderRemovedList').find("li[data-status]").each(function(){
	$tmp = $(this);
//	app.u.dump(" -> PID: "+$tmp.attr('data-pid')+" status: "+$tmp.attr('data-status'));
	if($tmp.attr('data-status') == 'complete')	{
		uCount += 1;
		$tmp.removeAttr('data-status'); //get rid of this so additional saves from same session are not impacted.
		}
	else if($tmp.attr('data-status') == 'error')	{
		eCount += 1;
		eReport += "<li>"+$tmp.attr('data-pid')+": "+app.data[$tmp.attr('data-pointer')].errmsg+" ("+app.data[$tmp.attr('data-pointer')].errid+"<\/li>";
		}
	});

app.u.dump(" -> items updated: "+uCount);
app.u.dump(" -> errors: "+eCount);
if(uCount > 0)	{
	$('#finderMessaging').prepend(app.u.formatMessage({'message':'Items Updated: '+uCount,'htmlid':'finderRequestResponse','uiIcon':'check','timeoutFunction':"$('#finderRequestResponse').slideUp(1000);"}))
	}

if(eCount > 0)	{
	$('#finderMessaging').prepend(app.u.formatMessage(eCount+' errors occured!<ul>'+eReport+'<\/ul>'));
	}

app.ext.admin.u.changeFinderButtonsState('enable'); //make buttons clickable



				},
			onError : function(responseData)	{
				responseData.parentID = "finderMessaging";
				app.u.throwMessage(responseData);
				app.ext.admin.u.changeFinderButtonsState('enable');
				}
			}, //finderChangesSaved
		
//callback is used for the product finder search results.
		showProdlist : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.showProdlist");
				if($.isEmptyObject(app.data[tagObj.datapointer]['@products']))	{
					$(app.u.jqSelector('#',tagObj.parentID)).empty().removeClass('loadingBG').append('Your search returned zero results');
					}
				else	{
//				app.u.dump(" -> parentID: "+tagObj.parentID);
//				app.u.dump(" -> datapointer: "+tagObj.datapointer);
				var numRequests = app.ext.store_prodlist.u.buildProductList({
"templateID":"adminProdStdForList",
"parentID":tagObj.parentID,
"items_per_page":100,
"csv":app.data[tagObj.datapointer]['@products']
					});
//				app.u.dump(" -> numRequests = "+numRequests);
					if(numRequests)
						app.model.dispatchThis();
					}
				}
			}, //showProdlist
		
			
//executed as part of a finder update for a category page. this is executed for each product.
//it simply changes the data-status appropriately, then the classback "finderChangesSaved" loops through the lists and handles messaging for all the updates.
		finderProductUpdate : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.finderProductUpdate.onSuccess");
//				app.u.dump(app.data[tagObj.datapointer]);
				var tmp = tagObj.datapointer.split('|'); // tmp1 is command and tmp1 is path and tmp2 is pid
				var targetID = tmp[0] == 'adminNavcatProductInsert' ? "finderTargetList" : "finderRemovedList";
				targetID += "_"+tmp[2];
//				app.u.dump(" -> targetID: "+targetID);
				$(app.u.jqSelector('#',targetID)).attr('data-status','complete');
				},
			onError : function(d)	{
//				app.u.dump("BEGIN admin.callbacks.finderProductUpdate.onError");
				var tmp = app.data[tagObj.datapointer].split('|'); // tmp0 is call, tmp1 is path and tmp2 is pid
//on an insert, the li will be in finderTargetList... but on a remove, the li will be in finderRemovedList_...
				var targetID = tmp[0] == 'adminNavcatProductInsert' ? "finderTargetList" : "finderRemovedList";
				
				targetID += "_"+tmp[2];
				$(app.u.jqSelector('#',targetID)).attr({'data-status':'error','data-pointer':tagObj.datapointer});
//				app.u.dump(d);
				}
			}, //finderProductUpdate




		filterFinderSearchResults : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.filterFinderSearchResults");
				var safePath = app.u.makeSafeHTMLId(tagObj.path);
				var $tmp;
//				app.u.dump(" -> safePath: "+safePath);
				//go through the results and if they are already in this category, disable drag n drop.
				$results = $('#finderSearchResults');
				//.find( "li" ).addClass( "ui-corner-all" ) )

				$results.find('li').each(function(){
					$tmp = $(this);
					if($('#finderTargetList_'+$tmp.attr('data-pid')).length > 0)	{
				//		app.u.dump(" -> MATCH! disable dragging.");
						$tmp.addClass('ui-state-disabled');
						}
					})
				}
			} //filterFinderSearchResults



		}, //callbacks

	
		
////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
		
		
	renderFormats : {
		
		elastimage1URL : function($tag,data)	{
//				app.u.dump(data.value[0]);
//				var L = data.bindData.numImages ? data.bindData.numImages : 1; //default to only showing 1 image.
//				for(var i = 0; i < L; i += 1)	{
//					}
			$tag.attr('src',app.u.makeImage({"name":data.value[0],"w":50,"h":50,"b":"FFFFFF","tag":0}));
			},
		
	
		array2ListItems : function($tag,data)	{
			var L = data.value.length;
			app.u.dump(" -> cleanValue for array2listItems");
			app.u.dump(data.value);
			var $o = $("<ul />"); //what is appended to tag. 
			for(var i = 0; i < L; i += 1)	{
				$o.append("<li>"+data.value[i]+"<\/li>");
				}
			$tag.append($o.children());
			},
		
		array2Template : function($tag,data)	{
//			app.u.dump("BEGIN admin.renderFormats.array2Template");
//			app.u.dump(data.value);
			var L = data.value.length;
			for(var i = 0; i < L; i += 1)	{
				$tag.append(app.renderFunctions.transmogrify({},data.bindData.loadsTemplate,data.value[i])); 
				}
			},
//a value, such as media library folder name, may be a path (my/folder/name) and a specific value from that string may be needed.
//set bindData.splitter and the value gets split on that character.
//optionally, set bindData.index to get a specific indices value (0,1, etc). if index is not declared, the last index is returned.
		getIndexOfSplit : function($tag, data){
			var splitter = data.bindData['splitter'];
			if(data.value.indexOf(splitter) > -1)	{
				var splitVal = data.value.split(splitter);
				data.value = splitVal[data.bindData.index || splitVal.length - 1]
				}
			else	{} //no split is occuring. do nothing.
			app.renderFormats.text($tag, data)
			}
		}, //renderFormats





////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
		a : {

//tmp while in dev so pushes can occur without UI being impacted.
//opts is options (options is a reserved JS name)
// -> opts.targetID is used within the function, but is not an accepted paramater (at this time) for being passed in.
//    it's in opts to make debugging easier.

			showUI : function(path,opts){
//make sure path passed in conforms to standard naming conventions.
// app.u.dump("BEGIN admin.a.showUI ["+path+"]");

				if(path)	{
//mode is either app or legacy. mode is required and generated based on path.
					var mode = undefined;
					if(path.substr(0,5) == "/biz/") {mode = 'legacy'}
					else if(path.substr(0,2) == "#:")	{mode = 'tabClick'} //path gets changed, so a separate mode is used for tracking when reloadTab is needed.
					else if (path.substr(0,2) == "#!") {mode = 'app'}
					else	{}
					
					if(mode)	{

if(path.substr(0,2) == "#:")	{
	path = "/biz/"+path.substring(2)+"/index.cgi";
	}

//app.u.dump(" -> mode: "+mode);
//app.u.dump(" -> path: "+path);

var reloadTab = 0; //used in conjunction with #: to determine if new or old contens should be displayed.
var $target = undefined; //jquery object of content destination

opts = opts || {}; //opts may b empty. treat as object.

_ignoreHashChange = true; //see handleHashChange for details on what this does.
document.location.hash = path; //update hash on URI.

//app.u.dump(" -> opts: "); app.u.dump(opts);

//if necessary get opt.tab defined. If at the end of code opt.tab is set, a tab will be brought into focus (in the header).
if(opts.tab){} // if tab is specified, always use it.
else if(mode == 'app')	{} //apps load into whatever content area is open, unless opt.tab is defined.
else if(opts.dialog)	{} //dialogs do not effect tab, unless opt.tab is defined.
else if(mode == 'legacy' || mode == 'tabClick'){
	opts.tab = app.ext.admin.u.getTabFromPath(path);
	} //#: denotes to open a tab, but not refresh the content.
else	{
	//hhmm. how did we get here?
	}

if(opts.tab)	{app.ext.admin.u.bringTabIntoFocus(opts.tab);} //changes which tab in the header is in focus.
else	{} //do nothing. perfectly normal to not change what tab is in focus.


//app.u.dump(" -> tab: "+opts.tab);

//set the targetID and $target for the content. 
// By now, tab will be set IF tab is needed. (dialog and/or app mode support no tab specification)
//this is JUST setting targetID. it isn't showing content or opening modals.
if(opts.dialog){
	opts.targetID = 'uiDialog';
	$target = app.ext.admin.u.handleCompatModeDialog(opts); //jquery object is returned by this function.
	}
//load content into whatever tab is specified.
else if(opts.tab)	{
	opts.targetID = opts.tab+"Content";
	$target = $(app.u.jqSelector('#',opts.targetID));
	} 
else if(app.ext.admin.vars.tab)	{
	opts.targetID = app.ext.admin.vars.tab+"Content";
	$target = $(app.u.jqSelector('#',opts.targetID));
	} //load into currently open tab. common for apps.
else	{
	//not in an app. no tab specified. not in modal. odd. how did we get here? No $target will be set. error handling occurs in if($target) check below.
	}


if($target && $target.length)	{
	if(opts.dialog)	{
		app.ext.admin.u.handleShowSection(path,opts,$target); 
		}
	else	{
		app.ext.admin.u.bringTabContentIntoFocus($target); //will make sure $target is visible. if already visible, no harm.
		if(mode == 'app')	{
			app.ext.admin.u.loadNativeApp(path,opts);
			}
		else if(mode == 'legacy')	{
			app.ext.admin.u.handleShowSection(path,opts,$target);
			}
		else if(mode == 'tabClick')	{
//determine whether new content is needed or not. typically, #: is only run from a tab so that when returning to  the tab, the last open content shows up.
			if(opts.tab == app.ext.admin.vars.tab)	{reloadTab = 1; } //tab clicked from a page within that tab. show new content.
			if ($target.children().length === 0)	{ reloadTab = 1; } //no content presently in target. load it.
			if(reloadTab)	{app.ext.admin.u.handleShowSection(path,opts,$target);}
			else	{} //show existing content. content area is already visible thanks to bringTabContentIntoFocus
			}
		else	{}// should never get here. error case for mode not being set is already passed.
		if(opts.tab)	{app.ext.admin.vars.tab = opts.tab;} //do this last so that the previously selected tab can be referenced, if needed.
		}
	}
else	{
	app.u.throwGMessage("Warning! target could not be found or does not exist on the DOM for admin.a.showUI.");
	}
						} //end 'if' for mode.
					else	{
						app.u.throwGMessage("Warning! unable to determine 'mode' in admin.a.showUI. path: "+path);	
						}
					
					}
				else	{
					app.u.throwGMessage("Warning! path not set for admin.a.showUI");
					}
				return false;
				},

/*
A generic form handler. 
$form is a jquery object of the form.
Either _cmd or call must be set in the form data (as hidden, for instance).
 -> _cmd will take the entire serialized form into a dispatch (see note on _tag below).
 -> call should be formatted as extension/call (ex: admin_task/adminTaskUpdate)
If you want to set any _tag attributes, set them as hidden inputs, like so:  <input type='hidden' name='_tag/something' value='someval'> 
 -> these would get formatted as _tag : {'something':'someval'}

Execute your own dispatch. This allows the function to be more versatile
set as onSubmit="app.ext.admin.a.processForm($(this)); app.model.dispatchThis('mutable'); return false;"
 -> if data-q is set to passive or immutable, change the value of dispatchThis to match.
*/
				processForm : function($form,q)	{
					var obj = $form.serializeJSON() || {};
					if($form.length && (obj._cmd || obj.call))	{
//						app.u.dump(" -> admin.a.processForm data attributes: "); app.u.dump(data);
						var _tag = {};
//build the _tag obj.
						for(key in obj)	{
							if(key.substring(0,5) == "_tag/")	{
								_tag[key.substring(5)] = obj[key];//_tag/ must be stripped from key.
								delete obj[key]; //remove from object so it isn't part of query.
								}
							else{}
							}
						app.u.dump(" -> _tag in processForm: "); app.u.dump(_tag);
						if(obj._cmd)	{
							obj._tag = _tag; //when adding straight to Q, _tag should be a param in the cmd object.
							app.model.addDispatchToQ(obj,q);
							}
						else if(obj.call)	{
							var call = obj.call; //save to another var. obj.call needs to be deleted so it isn't passed in dispatch.
							delete obj.call;
							
							app.ext[call.split('/')[0]].calls[call.split('/')[1]].init(obj,_tag,q)
							}
						else{} //can't get here. either cmd or call are set by now.
						
						}
					else	{
						app.u.throwGMessage("Warning! $form was empty or _cmd or call not present within $form in admin.a.processForm");
						}
					}, //processForm
				

//this is a function that brian has in the UI on some buttons.
//it's diferent than showUI so we can add extra functionality if needed.
//the app itself should never use this function.
			navigateTo : function(path,$t)	{
				return this.showUI(path,$t ? $t : {});
				},
				
			showDomainConfig : function(){
				$(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")).empty().showLoading();
				app.ext.admin.calls.adminDomainList.init({'callback':'showDomainConfig','extension':'admin'},'immutable');
				app.model.dispatchThis('immutable')
				},
			
//pass in a domain and an attr
//ex: pass in prt and the partition is returned.
			getDataForDomain : function(domain,attr)	{
				var r = false; //what is returned. will be value, if available.
				var data = app.data['adminDomainList']['@DOMAINS']; //shortcut
				var L = data.length;
				for(var i = 0; i < L; i += 1)	{
					if(data[i].id == domain){
						r = data[i][attr];
						break; //once a match is found, exit.
						}
					else{} //catch.
					}
				return r;
				}, //getDataForDomain

//host is www.zoovy.com.  domain is zoovy.com or m.zoovy.com.  This function wants a domain.
//changeDomain(domain,partition,path). partition and path are optional. If you have the partition, pass it to avoid me looking it up.
			changeDomain : function(domain,partition,path){
				if(domain)	{
					app.vars.domain = domain;
					$('.domain','#appView').text(domain);
//					app.rq.push(['script',0,'http://'+domain+'/jquery/config.js']); //load zGlobals. saves over existing values.
					if(partition){}
					else	{
						partition = app.ext.admin.a.getDataForDomain(domain,'prt');
						}
					$('.partition','#appView').text(partition || "");
	//get entire auth localstorage var (flattened on save, so entire object must be retrieved and saved)
	//something here is causing session to not persist on reload.
					if(app.model.fetchData('authAdminLogin'))	{
						var localVars = app.data['authAdminLogin'];
						localVars.domain = domain;
						localVars.partition = partition || null;
						app.storageFunctions.writeLocal('authAdminLogin',localVars);
						}
					showUI(path || "/biz/setup/index.cgi");
					}
				else	{
					app.u.throwGMessage("WARNING! admin.a.changeDomain required param 'domain' not passed. Yeah, can't change the domain without a domain.");
					}

				}, //changeDomain



//used in the builder for when 'edit' is clicked on an element.
//Params are set by B. This is for legacy support in the UI.

			loadElement : function(type,eleID){
				
				app.ext.admin.calls.adminUIBuilderPanelExecute.init({'sub':'EDIT','id':eleID},{'callback':'showElementEditorHTML','extension':'admin','targetID':'elementEditorContent'});
				app.model.dispatchThis();
				var $editor = $('#elementEditor');
				if($editor.length)	{
					$('#elementEditorMessaging',$editor).empty(); //modal already exists. empty previous content. Currently, one editor at a time.
					$('#elementEditorContent',$editor).empty().addClass('loadingBG');
					} 
				else	{
					$editor = $("<div \/>").attr('id','elementEditor').appendTo('body');
//within the editor, separate messaging/content elements are created so that one can be updated without affecting the other.
//especially impotant on a save where the messaging may get updated and the editor too, and you don't want to nuke the messaging on content update,
// which would happen if only one div was present.
					$editor.append("<div id='elementEditorMessaging'><\/div><div id='elementEditorContent' class='loadingBG'><\/div>").dialog({autoOpen:false,dialog:true, width: 500, height:500,modal:true});
					}
				$editor.dialog('open');
				}, //loadElement


			
//run on a select list inside 'edit' for a product list element.
//various select lists change what other options are available.
//t is 'this' from the select.
//ID is the element ID

			uiProdlistEditorUpdate : function(t,ID)	{
				app.ext.admin.u.uiSaveBuilderElement($("#editorForm"),ID,{'callback':'showMessaging','targetID':'elementEditorMessaging','message':'Saved. Panel updated to reflect new choices.'});
				app.ext.admin.calls.adminUIBuilderPanelExecute.init({'sub':'EDIT','id':ID},{'callback':'showElementEditorHTML','extension':'admin','targetID':'elementEditorContent'});
				app.model.dispatchThis();
				$('#elementEditorContent').empty().append("<div class='loadingBG'><\/div>");
				
				}, //uiProdlistEditorUpdate

/*
to generate an instance of the finder, run: 
app.ext.admin.a.addFinderTo() passing in targetID (the element you want the finder appended to) and path (a cat safe id or list id)

*/
			addFinderTo : function(targetID,findertype,path,attrib)	{
				app.u.dump("BEGIN admin.u.addFinderTo");
				app.u.dump(" -> findertype: "+findertype);
				app.u.dump(" -> path: "+path);
				app.u.dump(" -> attrib: "+attrib);
				if(findertype == 'PRODUCT')	{
					app.ext.store_product.calls.appProductGet.init(path,{"callback":"addPIDFinderToDom","extension":"admin","targetID":targetID,"path":path})
					}
				else if(findertype == 'NAVCAT')	{
//Too many f'ing issues with using a local copy of the cat data.
					app.ext.admin.calls.navcats.appCategoryDetailNoLocal.init(path,{"callback":"addFinderToDom","extension":"admin","targetID":targetID})
					}
				else if(findertype == 'PAGE')	{
					app.ext.admin.calls.appPageGet.init({'PATH':path,'@get':[attrib]},{"attrib":attrib,"path":path,"callback":"addPageFinderToDom","extension":"admin","targetID":targetID})			
					}
				else	{
					app.u.throwGMessage("Warning! Type param for admin.a.addFinderTo is invalid. ["+findertype+"]");
					}
				app.model.dispatchThis();
				}, //addFinderTo
				
//opens a dialog with a list of domains for selection.
//a domain being selected for their UI experience is important, so the request is immutable.
//a domain is necessary so that API knows what data to respond with, including profile and partition specifics.
//though domainChooserDialog is the element that's used, it's passed in the callback anyway for error handling purposes.
			showDomainChooser : function(){
//				app.u.dump("BEGIN admin.a.showDomainChooser");
				$('#domainChooserDialog').dialog('open').showLoading();
				app.ext.admin.calls.adminDomainList.init({'callback':'handleDomainChooser','extension':'admin','targetID':'domainChooserDialog'},'immutable'); 
				app.model.dispatchThis('immutable');
				},	 //showDomainChooser
				
//path - category safe id or product attribute in data-bind format:    product(zoovy:accessory_products)
			showFinderInModal : function(findertype,path,attrib)	{
				var $finderModal = $('#prodFinder');
//a finder has already been opened. empty it.
				if($finderModal.length > 0)	{
					$finderModal.empty();
					}
				else	{
					$finderModal = $('<div \/>').attr({'id':'prodFinder','title':'Product Finder'}).appendTo('body');
					}
//if the finder is for a product attribute, then add a data-sku so we can easily get the sku at any point.
//likewise, if it is NOT for a product, remove the data-pid (which may be present for a previously opened finder) to avoid any confusion down the road.
//data-pid is not used to avoid confusion. it's used on the li items in all the lists to denote which product they contain.
				$finderModal.attr('data-findertype',findertype);
				$finderModal.attr('data-path',path);
				$finderModal.attr('data-attrib',attrib);
				// if (type =='PRODUCT') {
					// var sku = path; 
					// if(sku){$finderModal.attr('data-sku',sku)}
					// else{$finderModal.removeAttr('data-sku')}
					// }
				
				$finderModal.dialog({modal:true,width:'94%',height:650});
				app.ext.admin.a.addFinderTo('prodFinder',findertype,path,attrib);
				}, //showFinderInModal

			login : function($form){
				$('#preloadAndLoginContents').showLoading();
				app.calls.authentication.accountLogin.init($form.serializeJSON(),{'callback':'showHeader','extension':'admin'});
				app.model.dispatchThis('immutable');
				}, //login

			logout : function(){
				$('body').showLoading();
				app.calls.authentication.authAdminLogout.init({'callback':'handleLogout','extension':'admin'});//always immutable.
				app.model.dispatchThis('immutable');
//nuke all this after the request so that the dispatch has the info it needs.
				app.ext.admin.u.selectivelyNukeLocalStorage(); //get rid of most local storage content. This will reduce issues for users with multiple accounts.
				app.model.destroy('authAdminLogin'); //clears this out of memory and local storage. This would get used during the controller init to validate the session.

				} //logout

			}, //action





////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\


		u : {
//executed after preloader if device is logged in.
//executed after login if a login is required.
//If a domain hasn't been selected (from a previous session) then a prompt shows up to choose a domain.
//the entire UI experience revolves around having a domain.
			showHeader : function(){
//				$('#appPreView').hide();
//				$('#appLogin').hide();
				$('#appView').show();
				$('#preloadAndLoginContainer').hide(); //hide all preView and login data.
				$('#preloadAndLoginContents').hideLoading(); //make sure this gets turned off or it will be a layer over the content.
				$('.username','#appView').text(app.vars.username);
				var domain = this.getDomain();
//				app.u.dump(" -> DOMAIN: ["+domain+"]");

//show the domain chooser if one is not set. see showDomainChooser function for more info on why.
//if a domain is already set, this is a return visit. Get the list of domains  passively because they'll be used.
				if (!domain) {
					//the selection of a domain name will load the page content. (but we'll still need to nav)
					app.ext.admin.a.showDomainChooser(); 
					}
				else {
					app.ext.admin.calls.adminDomainList.init({},'passive');
					$('.domain','#appView').text(domain);
// load whatever page is set on the hash onload. The product page being first needs some help.
					app.ext.admin.a.showUI(window.location.hash ? window.location.hash.replace(/^#/, '') : '/biz/recent.cgi'); 
					}
				}, //showHeader


//used to determine what domain should be used. mostly in init, but could be used elsewhere.
			getDomain : function(){<commit_msg>admin - fix bug so refresh on #! or #: page works<commit_after>/* **************************************************************

   Copyright 2011 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


************************************************************** */

/*
An extension for working within the Zoovy UI.


finder -
'path' refers to either a category safe id (.safe.name) or a list safe id ($mylistid)
'safePath' is used for a jquery friendly id. ex: .safe.name gets converted to _safe_name and $mylistid to mylistid).


NOTE
 - admin 'set' calls are hard coded to use the immutable Q so that a dispatch is not overridden.
 - in the calls, 'dispatch' was removed and only init is present IF we never check local storage for the data.
 
 
calls
 -> init should contain any code necessary for checking localStorage or, when supported, local DB.
 -> dispatch should contain everything needed for the dispatch, so that if it is executed instead of init, it works fine. 
*/


var admin = function() {
// theseTemplates is it's own var because it's loaded in multiple places.
// here, only the most commonly used templates should be loaded. These get pre-loaded. Otherwise, load the templates when they're needed or in a separate extension (ex: admin_orders)
	var theseTemplates = new Array('adminProdStdForList','adminProdSimpleForList','adminElasticResult','adminProductFinder','adminMultiPage','domainPanelTemplate','pageSetupTemplate'); 
	var r = {
		
	vars : {
		tab : null, //is set when switching between tabs. it outside 'state' because this doesn't get logged into local storage.
		tabs : ['setup','sites','jt','product','orders','crm','syndication','reports','utilities'],
		state : {},
		tab : 'home',
		templates : theseTemplates,
		willFetchMyOwnTemplates : true,
		"tags" : ['IS_FRESH','IS_NEEDREVIEW','IS_HASERRORS','IS_CONFIGABLE','IS_COLORFUL','IS_SIZEABLE','IS_OPENBOX','IS_PREORDER','IS_DISCONTINUED','IS_SPECIALORDER','IS_BESTSELLER','IS_SALE','IS_SHIPFREE','IS_NEWARRIVAL','IS_CLEARANCE','IS_REFURB','IS_USER1','IS_USER2','IS_USER3','IS_USER4','IS_USER5','IS_USER6','IS_USER7','IS_USER8','IS_USER9'],
		"dependencies" : ['store_prodlist','store_navcats','store_product','store_search'] //a list of other extensions (just the namespace) that are required for this one to load
		},



					////////////////////////////////////   CALLS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\		



	calls : {


		appResource : {
			init : function(filename)	{
				this.dispatch(filename);
				},
			dispatch : function(filename)	{
				app.model.addDispatchToQ({"_cmd":"appResource","filename":filename,"_tag" : {"datapointer":"adminImageFolderList"}});	
				}
			
			}, 

			/*
a typical 'fetchData' is done for a quick determination on whether or not ANY data for the category is local.
if not, we're obviously missing what's needed.  If some data is local, check for the presence of the attributes
requested and if even one isn't present, get all.
datapointer needs to be defined early in the process so that it can be used in the handlecallback, which happens in INIT.
obj.PATH = .cat.safe.id
*/
		appPageGet : {
			init : function(obj,tagObj,Q)	{
				obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
				obj['_tag'].datapointer = 'appPageGet|'+obj.PATH;  //no local storage of this. ### need to explore solutions.
				
				this.dispatch(obj,tagObj,Q);
				r = 1;
				return r;
				},
			dispatch : function(obj,tagObj,Q)	{
				obj['_cmd'] = "appPageGet";
				app.model.addDispatchToQ(obj,Q);
				}
			}, //appPageGet
			
		appPageSet : {
			init : function(obj,tagObj,Q)	{
				this.dispatch(obj,tagObj,Q);				
				},
			dispatch : function(obj,tagObj,Q)	{
				obj._tag = tagObj;
				obj._cmd = 'appPageSet';
				app.model.addDispatchToQ(obj,Q);
				}			
			},

//the appSettings 'domains' in the response are hard coded to app.domain...
//probably should avoid using the appSettings for now. rootcat is probably safe.
		appConfig : {
			init : function(tagObj,Q)	{
				this.dispatch(tagObj,Q);				
				},
			dispatch : function(tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"appConfig","_tag" : tagObj},Q);
				}			
			},

		adminDomainList : {
			init : function(tagObj,Q)	{
//			app.u.dump("BEGIN admin.calls.adminDomainList");
				tagObj = tagObj || {};
				tagObj.datapointer = "adminDomainList";
				if(app.model.fetchData(tagObj.datapointer) == false)	{
					r = 1;
					this.dispatch(tagObj,Q);
					}
				else	{
					app.u.handleCallback(tagObj);
					}
				},
			dispatch : function(tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"adminDomainList","_tag" : tagObj},Q);
				}			
			},



//obj requires panel and pid and sub.  sub can be LOAD or SAVE
			adminUIDomainPanelExecute : {
				init : function(obj,tagObj,Q)	{
					tagObj = tagObj || {};
//save and load 'should' always have the same data, so the datapointer is shared.
					tagObj.datapointer = "adminUIDomainPanelExecute|"+obj.domain+"|"+obj.verb;
					this.dispatch(obj,tagObj,Q);
					},
				dispatch : function(obj,tagObj,Q)	{
					obj['_cmd'] = "adminUIDomainPanelExecute";
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);	
					}
				}, //adminUIProductPanelList



		navcats : {
//app.ext.admin.calls.navcats.appCategoryDetailNoLocal.init(path,{},'immutable');
			appCategoryDetailNoLocal : {
				init : function(path,tagObj,Q)	{
					tagObj = typeof tagObj !== 'object' ? {} : tagObj;
					tagObj.datapointer = 'appCategoryDetail|'+path;
					this.dispatch(path,tagObj,Q);
					return 1;
					},
				dispatch : function(path,tagObj,Q)	{
					app.model.addDispatchToQ({"_cmd":"appCategoryDetail","safe":path,"detail":"fast","_tag" : tagObj},Q);	
					}
				}//appCategoryDetail
			
			}, //navcats

			
		customer : {

			adminCustomerGet : {
				init : function(CID,tagObj,Q)	{
//					app.u.dump("CID: "+CID);
					var r = 0;
//if datapointer is fixed (set within call) it needs to be added prior to executing handleCallback (which needs datapointer to be set).
					tagObj = tagObj || tagObj;
					tagObj.datapointer = "adminCustomerGet|"+CID;
					if(app.model.fetchData(tagObj.datapointer) == false)	{
						r = 1;
						this.dispatch(CID,tagObj,Q);
						}
					else	{
						app.u.handleCallback(tagObj);
						}
					return r;
					},
				dispatch : function(CID,tagObj,Q)	{
//					app.u.dump("CID: "+CID);
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminCustomerGet";
					obj["CID"] = CID;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				},

//no local storage of this call. only 1 in memory. Will expand when using session storage if deemed necessary.
		adminCustomerLookup : {
			init : function(email,tagObj,Q)	{
				tagObj = tagObj || tagObj;
				tagObj.datapointer = "adminCustomerLookup";
				this.dispatch(email,tagObj,Q);
				},
			dispatch : function(email,tagObj,Q)	{
				app.model.addDispatchToQ({"_cmd":"adminCustomerLookup","email":email,"_tag" : tagObj});	
				}			
			},
			adminCustomerSet : {
				init : function(CID,setObj,tagObj)	{
					this.dispatch(CID,setObj,tagObj)
					return 1;
					},
				dispatch : function(CID,setObj,tagObj)	{
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminCustomerSet";
					obj["CID"] = CID;
					obj['%set'] = setObj;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,'immutable');
					}
				}
			},
			
		product : {
			appProductGetNoLocal : {
				init : function(pid,tagObj,Q)	{
					this.dispatch(pid,tagObj,Q)
					return 1;
					},
				dispatch : function(pid,tagObj,Q)	{
					var obj = {};
					tagObj = tagObj || {};
					tagObj["datapointer"] = "appProductGet|"+pid; 
					obj["_cmd"] = "appProductGet";
					obj["pid"] = pid;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				},//appProductGetNoLocal
//app.ext.admin.calls.product.adminProductUpdate.init(pid,attrObj,tagObj,Q)
			adminProductUpdate : {
				init : function(pid,attrObj,tagObj)	{
					this.dispatch(pid,attrObj,tagObj)
					return 1;
					},
				dispatch : function(pid,attrObj,tagObj)	{
					var obj = {};
					tagObj = tagObj || {};
					obj["_cmd"] = "adminProductUpdate";
					obj["pid"] = pid;
					obj['%attribs'] = attrObj;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,'immutable');
					}
				}
				
			}, //product
		finder : {
			
			adminNavcatProductInsert : {
				init : function(pid,position,path,tagObj)	{
					this.dispatch(pid,position,path,tagObj);
					return 1;
					},
				dispatch : function(pid,position,path,tagObj)	{
					var obj = {};
					obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
					obj['_cmd'] = "adminNavcatProductInsert";
					obj.pid = pid;
					obj.path = path;
					obj.position = position;
					obj['_tag'].datapointer = "adminNavcatProductInsert|"+path+"|"+pid;
					app.model.addDispatchToQ(obj,'immutable');	
					}
				}, //adminNavcatProductInsert
			
			adminNavcatProductDelete : {
				init : function(pid,path,tagObj)	{
					this.dispatch(pid,path,tagObj);
					},
				dispatch : function(pid,path,tagObj)	{
					var obj = {};
					obj['_tag'] = typeof tagObj == 'object' ? tagObj : {};
					obj['_cmd'] = "adminNavcatProductDelete";
					obj.pid = pid;
					obj.path = path;
					obj['_tag'].datapointer = "adminNavcatProductDelete|"+path+"|"+pid;
					app.model.addDispatchToQ(obj,'immutable');	
					}
				} //adminNavcatProductDelete
			
			}, //finder




//obj requires sub and sref.  sub can be LOAD or SAVE
//reload is also supported.
			adminUIBuilderPanelExecute : {
				init : function(obj,tagObj,Q)	{
					tagObj = tagObj || {};
					if(obj['sub'] == 'EDIT')	{
						tagObj.datapointer = "adminUIBuilderPanelExecute|edit";
						}
					else if(obj['sub'] == 'SAVE')	{
						tagObj.datapointer = "adminUIBuilderPanelExecute|save";
						}
					else	{
						//catch. some new verb or a format that doesn't require localStorage.
						}
					this.dispatch(obj,tagObj,Q);
					},
				dispatch : function(obj,tagObj,Q)	{
					obj['_cmd'] = "adminUIBuilderPanelExecute";
					obj['_SREF'] = sref;
					obj["_tag"] = tagObj;
					app.model.addDispatchToQ(obj,Q);
					}
				} //adminUIProductPanelList

		}, //calls




					////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\





	callbacks : {
//callbacks.init need to return either a true or a false, depending on whether or not the file will execute properly based on store account configuration. Use this for any config or dependencies that need to occur.
//the callback is auto-executed as part of the extensions loading process.
		init : {
			onSuccess : function()	{
//				app.u.dump('BEGIN app.ext.admin.init.onSuccess ');
				var r = true; //return false if extension can't load. (no permissions, wrong type of session, etc)
//app.u.dump("DEBUG - template url is changed for local testing. add: ");

app.model.fetchNLoadTemplates(app.vars.baseURL+'extensions/admin/templates.html',theseTemplates);

app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/styles.css','admin_styles']);
app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/legacy_compat.js']);

app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/legacy_compat.js']);



/* used for html editor. */
app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/style/jHtmlArea.ColorPickerMenu.css','jHtmlArea_ColorPickerMenu']);
app.rq.push(['css',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/style/jHtmlArea.css','jHtmlArea']);
//note - the editor.css file that comes with jhtmlarea is NOT needed. just sets the page bgcolor to black.

// colorpicker isn't loaded until jhtmlarea is done to avoid a js error due to load order.
app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/scripts/jHtmlArea-0.7.5.min.js',function(){app.rq.push(['script',0,app.vars.baseURL+'extensions/admin/resources/jHtmlArea-0.7.5.ExamplePlusSource/scripts/jHtmlArea.ColorPickerMenu-0.7.0.min.js'])}]);




				return r;
				},
			onError : function(d)	{
//init error handling handled by controller.				
				}
			}, //init

		

//executed when the extension loads
		initExtension : {
			onSuccess : function()	{
//				app.u.dump('BEGIN app.ext.admin.initUserInterface.onSuccess ');
				var L = app.rq.length-1;
//load any remaining resources into the app.
				for(var i = L; i >= 0; i -= 1)	{
					app.u.handleResourceQ(app.rq[i]);
					app.rq.splice(i, 1); //remove once handled.
					}
				app.rq.push = app.u.handleResourceQ; //reassign push function to auto-add the resource.

if(app.u.getBrowserInfo().substr(0,4) == 'msie' && parseFloat(navigator.appVersion.split("MSIE")[1]) < 10)	{
	app.u.throwMessage("<p>In an effort to provide the best user experience for you and to also keep our development team sane, we've opted to optimize our user interface for webkit based browsers. These include; Safari, Chrome and FireFox. Each of these are free and provide a better experience, including more diagnostics for us to maintain our own app framework.<\/p><p><b>Our store apps support IE8+<\/b><\/p>");
	}

if(app.u.getParameterByName('debug'))	{
	$('button','#debugPanel').button();
	$('#debugPanel').show()
	$('.debugContent','#debugPanel').append("<div class='clearfix'>Model Version: "+app.model.version+" and release: "+app.vars.release+"</div>");
	$('body').css('padding-bottom',125);
	$('#jtSectionTab').show();
	}

//get list of domains and show chooser.
				var $domainChooser = $("<div \/>").attr({'id':'domainChooserDialog','title':'Choose a domain to work on'}).addClass('displayNone').appendTo('body');
				$domainChooser.dialog({
					'autoOpen':false,
					'modal':true,
					'width': '90%',
					'height': 500,
					'closeOnEscape': false,
					open: function(event, ui) {$(".ui-dialog-titlebar-close", $(this).parent()).hide();} //hide 'close' icon.
					});


//make sure all the links in the header use the proper syntax.
				$('.bindByAnchor','#mastHead').each(function(){
					// app.u.dump("BEGIN #mastHead rewriteLink");
					app.ext.admin.u.rewriteLink($(this));
					})
//if supported, update hash while navigating.
// see handleHashState function for what this is and how it works.
				if("onhashchange" in window)	{ // does the browser support the hashchange event?
//					app.u.dump("WOOT! browser supports hashchange");
					_ignoreHashChange = false; //global var. when hash is changed from JS, set to true. see handleHashState for more info on this.
					window.onhashchange = function () {
//						app.u.dump("Hash has changed.");
						app.ext.admin.u.handleHashState();
						}
					}
	
//create shortcuts. these are used in backward compatibility areas where brian loads the content.
				window.navigateTo = app.ext.admin.a.navigateTo;
				window.showUI = app.ext.admin.a.showUI;
				window.loadElement = app.ext.admin.a.loadElement;
				window.prodlistEditorUpdate = app.ext.admin.a.uiProdlistEditorUpdate;
				window.changeDomain = app.ext.admin.a.changeDomain;
				window.linkOffSite = app.ext.admin.u.linkOffSite;
				window.adminUIDomainPanelExecute = app.ext.admin.u.adminUIDomainPanelExecute;
				window._ignoreHashChange = false; // see handleHashState to see what this does.
				


//if user is logged in already (persistant login), take them directly to the UI. otherwise, have them log in.
//the code for handling the support login is in the thisisanadminsession function (looking at uri)
if(app.u.thisIsAnAdminSession())	{
	app.ext.admin.u.showHeader();
	}
else	{
	$('#appPreView').hide();
	$('#appLogin').show();
	}
				}
			},

		showDataHTML : {
			onSuccess : function(tagObj)	{
//				app.u.dump("SUCCESS!"); app.u.dump(tagObj);
				$(app.u.jqSelector('#',tagObj.targetID)).removeClass('loadingBG').hideLoading().html(app.data[tagObj.datapointer].html); //.wrap("<form id='bob'>");
				}
			}, //showDataHTML


		handleLogout : {
			onSuccess : function(tagObj)	{
				document.location = 'logout.html'
				}
			},
//in cases where the content needs to be reloaded after making an API call, but when a showUI directly won't do (because of sequencing, perhaps)
//For example, after new files are added to a ticket (comatability mode), this is executed on a ping to update the page behind the modal.
		showUI : {
			onSuccess : function(tagObj)	{
				if(tagObj && tagObj.path){showUI(tagObj.path)
					}
				else {
					app.u.throwGMessage("Warning! Invalid path specified in _rtag on admin.callbacks.showUI.onSuccess.");
					app.u.dump("admin.callbacks.showUI.onSuccess tagObj (_rtag)");
					app.u.dump(tagObj);
					}
				}
			}, //showUI
		showDomainConfig : {
			onSuccess : function(){
				app.ext.admin.u.domainConfig();
				}
			},

		showElementEditorHTML : {
			onSuccess : function(tagObj)	{
//				app.u.dump("SUCCESS!"); app.u.dump(tagObj);
				var $target = $(app.u.jqSelector('#',tagObj.targetID))
				$target.parent().find('.ui-dialog-title').text(app.data[tagObj.datapointer]['prompt']); //add title to dialog.
				var $form = $("<form \/>").attr('id','editorForm'); //id used in product edit mode.
				$form.submit(function(event){
					event.preventDefault(); //do not post form.
					app.ext.admin.u.uiSaveBuilderElement($form,app.data[tagObj.datapointer].id,{'callback':'handleElementSave','extension':'admin','targetID':'elementEditorMessaging'})
					app.model.dispatchThis();
					return false;
					}).append(app.data[tagObj.datapointer].html);
				$form.append("<div class='marginTop center'><input type='submit' class='ui-state-active' value='Save' \/><\/div>");
				$target.removeClass('loadingBG').html($form);
				}
			}, //showElementEditorHTML


//executed after a 'save' is pushed for a specific element while editing in the builder.
		handleElementSave : {
			
			onSuccess : function(tagObj)	{
//First, let the user know the changes are saved.
				var msg = app.u.successMsgObject("Thank you, your changes are saved.");
				msg['_rtag'] = tagObj; //pass in tagObj as well, as that contains info for parentID.
				app.u.throwMessage(msg);

				if(app.ext.admin.vars.tab)	{
					app.u.dump("GOT HERE! app.ext.admin.vars.tab: "+app.ext.admin.vars.tab);
					$(app.u.jqSelector('#',app.ext.admin.vars.tab+'Content')).empty().append(app.data[tagObj.datapointer].html)
					}			
				}
			}, //handleElementSave

		showHeader : {
			onSuccess : function(){
				app.ext.admin.u.showHeader();
				},
			onError : function(responseData){
				app.u.throwMessage(responseData);
//				if(responseData.errid == "100")	{
//					app.u.throwMessage("This is most typically due to your system clock not being set correctly. For security, it must be set to both the correct time and timezone.");
//					} //this is the clock issue.
				$('#preloadAndLoginContents').hideLoading();
				}
			}, //showHeader

		handleDomainChooser : {
			onSuccess : function(tagObj){
//				app.u.dump("BEGIN admin.callbacks.handleDomainChooser.onSuccess");
				var data = app.data[tagObj.datapointer]['@DOMAINS'];
				var $target = $(app.u.jqSelector('#',tagObj.targetID));
				var L = data.length;
				if(L)	{
					var $ul = $('#domainList'); //ul in modal.
	//modal has been opened on this visit.  Domain list still reloaded in case they've changed.
					if($ul.length)	{$ul.empty()} //user is changing domains.
	//first time modal has been viewed.
					else	{
						$ul = $("<ul \/>").attr('id','domainList');
						}
					
					for(var i = 0; i < L; i += 1)	{
						$("<li \/>").data(data[i]).addClass('lookLikeLink').addClass(data[i].id == app.vars.domain ? 'ui-selected' : '').append(data[i].id+" [prt: "+data[i].prt+"]").click(function(){
							app.ext.admin.a.changeDomain($(this).data('id'),$(this).data('prt'))
							$target.dialog('close');
							}).appendTo($ul);
						}
					$target.hideLoading().append($ul);
					}
				else	{
//user has no domains on file. What to do?
					}
				},
			onError: function(responseData)	{
				var $target = $(app.u.jqSelector('#',responseData._rtag.targetID));
				$target.hideLoading();
				responseData.persistant = true;
				app.u.throwMessage(responseData);
				$target.append("<P>Something has gone very wrong. We apologize, but we were unable to load your list of domains. A domain is required.</p>");
				$("<button \/>").attr('title','Close Window').text('Close Window').click(function(){$target.dialog('close')}).button().appendTo($target);
				}
			}, //handleDomainChooser

		handleElasticFinderResults : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.handleElasticFinderResults.onSuccess.");
				var L = app.data[tagObj.datapointer]['_count'];
				$('#resultsKeyword').html(L+" results <span id='resultsListItemCount'></span>:");
//				app.u.dump(" -> Number Results: "+L);
				$parent = $(app.u.jqSelector('#',tagObj.parentID)).empty().removeClass('loadingBG')
				if(L == 0)	{
					$parent.append("Your query returned zero results.");
					}
				else	{
					var pid;//recycled shortcut to product id.
					for(var i = 0; i < L; i += 1)	{
						pid = app.data[tagObj.datapointer].hits.hits[i]['_id'];
//						app.u.dump(" -> "+i+" pid: "+pid);
						$parent.append(app.renderFunctions.transmogrify({'id':pid,'pid':pid},'adminElasticResult',app.data[tagObj.datapointer].hits.hits[i]['_source']));
						}
					app.ext.admin.u.filterFinderResults();
					}
				}
			}, //handleElasticFinderResults

/*
REMINDER!!!
Now that we have three params, findertype, path and attrib, we don't need three callbacks.
merge these into one.
JT - 2012-11-21 (on vaca)
*/

//callback executed after the navcat data is retrieved. the u, does most of the work.
		addFinderToDom : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callback.addFinderToDom.success");
				app.ext.admin.u.addFinder(tagObj.targetID,'NAVCAT',app.data[tagObj.datapointer].id);
				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer].pretty); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addFinderToDom

//callback executed after the navcat data is retrieved. the u.addfinder does most of the work.
		addPageFinderToDom : {
			onSuccess : function(tagObj)	{
				//app.u.dump("BEGIN admin.callback.addPageFinderToDom.success");
				//app.u.dump("app.data[tagObj.datapointer]"); app.u.dump(app.data[tagObj.datapointer]);
				app.ext.admin.u.addFinder(tagObj.targetID,'PAGE',app.data[tagObj.datapointer]._rtag.path,app.data[tagObj.datapointer]._rtag.attrib);
//				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer].pretty); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addFinderToDom

//callback executed after the appProductGet data is retrieved for creating a finder, specific to editing an attribute of a product (related items, for example)
		addPIDFinderToDom : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callback.addPIDFinderToDom.success");
				app.ext.admin.u.addFinder(tagObj.targetID,'PRODUCT',tagObj.path,tagObj.datapointer.split('|')[1],$('#prodFinder').attr('data-attrib'));
				$('#prodFinder').parent().find('.ui-dialog-title').text('Product Finder: '+app.data[tagObj.datapointer]['%attribs']['zoovy:prod_name']+" ("+app.renderFunctions.parseDataVar(tagObj.path)+")"); //updates modal title
//				app.u.dump(tagObj);
				}
			}, //addPIDFinderToDom
			
//when a finder for a product attribute is executed, this is the callback.
		pidFinderChangesSaved : {
			onSuccess : function(tagObj)	{
				app.u.dump("BEGIN admin.callbacks.pidFinderChangesSaved");
				$('#finderMessaging').prepend(app.u.formatMessage({'message':'Your changes have been saved.','htmlid':'finderRequestResponse','uiIcon':'check','timeoutFunction':"$('#finderRequestResponse').slideUp(1000);"}))
				app.ext.admin.u.changeFinderButtonsState('enable'); //make buttons clickable
				},
			onError : function(responseData)	{
				responseData.parentID = "finderMessaging";
				app.u.throwMessage(responseData);
				app.ext.admin.u.changeFinderButtonsState('enable');
				}
			
			}, //pidFinderChangesSaved
//when a finder for a category/list/etc is executed...

		finderChangesSaved : {
			onSuccess : function(tagObj)	{


app.u.dump("BEGIN admin.callbacks.finderChangesSaved");
var uCount = 0; //# of updates
var eCount = 0; //# of errros.
var eReport = ''; // a list of all the errors.

var $tmp;

$('#finderTargetList, #finderRemovedList').find("li[data-status]").each(function(){
	$tmp = $(this);
//	app.u.dump(" -> PID: "+$tmp.attr('data-pid')+" status: "+$tmp.attr('data-status'));
	if($tmp.attr('data-status') == 'complete')	{
		uCount += 1;
		$tmp.removeAttr('data-status'); //get rid of this so additional saves from same session are not impacted.
		}
	else if($tmp.attr('data-status') == 'error')	{
		eCount += 1;
		eReport += "<li>"+$tmp.attr('data-pid')+": "+app.data[$tmp.attr('data-pointer')].errmsg+" ("+app.data[$tmp.attr('data-pointer')].errid+"<\/li>";
		}
	});

app.u.dump(" -> items updated: "+uCount);
app.u.dump(" -> errors: "+eCount);
if(uCount > 0)	{
	$('#finderMessaging').prepend(app.u.formatMessage({'message':'Items Updated: '+uCount,'htmlid':'finderRequestResponse','uiIcon':'check','timeoutFunction':"$('#finderRequestResponse').slideUp(1000);"}))
	}

if(eCount > 0)	{
	$('#finderMessaging').prepend(app.u.formatMessage(eCount+' errors occured!<ul>'+eReport+'<\/ul>'));
	}

app.ext.admin.u.changeFinderButtonsState('enable'); //make buttons clickable



				},
			onError : function(responseData)	{
				responseData.parentID = "finderMessaging";
				app.u.throwMessage(responseData);
				app.ext.admin.u.changeFinderButtonsState('enable');
				}
			}, //finderChangesSaved
		
//callback is used for the product finder search results.
		showProdlist : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.showProdlist");
				if($.isEmptyObject(app.data[tagObj.datapointer]['@products']))	{
					$(app.u.jqSelector('#',tagObj.parentID)).empty().removeClass('loadingBG').append('Your search returned zero results');
					}
				else	{
//				app.u.dump(" -> parentID: "+tagObj.parentID);
//				app.u.dump(" -> datapointer: "+tagObj.datapointer);
				var numRequests = app.ext.store_prodlist.u.buildProductList({
"templateID":"adminProdStdForList",
"parentID":tagObj.parentID,
"items_per_page":100,
"csv":app.data[tagObj.datapointer]['@products']
					});
//				app.u.dump(" -> numRequests = "+numRequests);
					if(numRequests)
						app.model.dispatchThis();
					}
				}
			}, //showProdlist
		
			
//executed as part of a finder update for a category page. this is executed for each product.
//it simply changes the data-status appropriately, then the classback "finderChangesSaved" loops through the lists and handles messaging for all the updates.
		finderProductUpdate : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.finderProductUpdate.onSuccess");
//				app.u.dump(app.data[tagObj.datapointer]);
				var tmp = tagObj.datapointer.split('|'); // tmp1 is command and tmp1 is path and tmp2 is pid
				var targetID = tmp[0] == 'adminNavcatProductInsert' ? "finderTargetList" : "finderRemovedList";
				targetID += "_"+tmp[2];
//				app.u.dump(" -> targetID: "+targetID);
				$(app.u.jqSelector('#',targetID)).attr('data-status','complete');
				},
			onError : function(d)	{
//				app.u.dump("BEGIN admin.callbacks.finderProductUpdate.onError");
				var tmp = app.data[tagObj.datapointer].split('|'); // tmp0 is call, tmp1 is path and tmp2 is pid
//on an insert, the li will be in finderTargetList... but on a remove, the li will be in finderRemovedList_...
				var targetID = tmp[0] == 'adminNavcatProductInsert' ? "finderTargetList" : "finderRemovedList";
				
				targetID += "_"+tmp[2];
				$(app.u.jqSelector('#',targetID)).attr({'data-status':'error','data-pointer':tagObj.datapointer});
//				app.u.dump(d);
				}
			}, //finderProductUpdate




		filterFinderSearchResults : {
			onSuccess : function(tagObj)	{
//				app.u.dump("BEGIN admin.callbacks.filterFinderSearchResults");
				var safePath = app.u.makeSafeHTMLId(tagObj.path);
				var $tmp;
//				app.u.dump(" -> safePath: "+safePath);
				//go through the results and if they are already in this category, disable drag n drop.
				$results = $('#finderSearchResults');
				//.find( "li" ).addClass( "ui-corner-all" ) )

				$results.find('li').each(function(){
					$tmp = $(this);
					if($('#finderTargetList_'+$tmp.attr('data-pid')).length > 0)	{
				//		app.u.dump(" -> MATCH! disable dragging.");
						$tmp.addClass('ui-state-disabled');
						}
					})
				}
			} //filterFinderSearchResults



		}, //callbacks

	
		
////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
		
		
	renderFormats : {
		
		elastimage1URL : function($tag,data)	{
//				app.u.dump(data.value[0]);
//				var L = data.bindData.numImages ? data.bindData.numImages : 1; //default to only showing 1 image.
//				for(var i = 0; i < L; i += 1)	{
//					}
			$tag.attr('src',app.u.makeImage({"name":data.value[0],"w":50,"h":50,"b":"FFFFFF","tag":0}));
			},
		
	
		array2ListItems : function($tag,data)	{
			var L = data.value.length;
			app.u.dump(" -> cleanValue for array2listItems");
			app.u.dump(data.value);
			var $o = $("<ul />"); //what is appended to tag. 
			for(var i = 0; i < L; i += 1)	{
				$o.append("<li>"+data.value[i]+"<\/li>");
				}
			$tag.append($o.children());
			},
		
		array2Template : function($tag,data)	{
//			app.u.dump("BEGIN admin.renderFormats.array2Template");
//			app.u.dump(data.value);
			var L = data.value.length;
			for(var i = 0; i < L; i += 1)	{
				$tag.append(app.renderFunctions.transmogrify({},data.bindData.loadsTemplate,data.value[i])); 
				}
			},
//a value, such as media library folder name, may be a path (my/folder/name) and a specific value from that string may be needed.
//set bindData.splitter and the value gets split on that character.
//optionally, set bindData.index to get a specific indices value (0,1, etc). if index is not declared, the last index is returned.
		getIndexOfSplit : function($tag, data){
			var splitter = data.bindData['splitter'];
			if(data.value.indexOf(splitter) > -1)	{
				var splitVal = data.value.split(splitter);
				data.value = splitVal[data.bindData.index || splitVal.length - 1]
				}
			else	{} //no split is occuring. do nothing.
			app.renderFormats.text($tag, data)
			}
		}, //renderFormats





////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
		a : {

//tmp while in dev so pushes can occur without UI being impacted.
//opts is options (options is a reserved JS name)
// -> opts.targetID is used within the function, but is not an accepted paramater (at this time) for being passed in.
//    it's in opts to make debugging easier.

			showUI : function(path,opts){
//make sure path passed in conforms to standard naming conventions.
// app.u.dump("BEGIN admin.a.showUI ["+path+"]");

				if(path)	{
//mode is either app or legacy. mode is required and generated based on path.
					var mode = undefined;
					if(path.substr(0,5) == "/biz/") {mode = 'legacy'}
					else if(path.substr(0,2) == "#:")	{mode = 'tabClick'} //path gets changed, so a separate mode is used for tracking when reloadTab is needed.
					else if (path.substr(0,2) == "#!") {mode = 'app'}
					else	{}
					
					if(mode)	{

if(path.substr(0,2) == "#:")	{
	path = "/biz/"+path.substring(2)+"/index.cgi";
	}

//app.u.dump(" -> mode: "+mode);
//app.u.dump(" -> path: "+path);

var reloadTab = 0; //used in conjunction with #: to determine if new or old contens should be displayed.
var $target = undefined; //jquery object of content destination

opts = opts || {}; //opts may b empty. treat as object.

_ignoreHashChange = true; //see handleHashChange for details on what this does.
document.location.hash = path; //update hash on URI.

//app.u.dump(" -> opts: "); app.u.dump(opts);

//if necessary get opt.tab defined. If at the end of code opt.tab is set, a tab will be brought into focus (in the header).
if(opts.tab){} // if tab is specified, always use it.
else if(mode == 'app')	{} //apps load into whatever content area is open, unless opt.tab is defined.
else if(opts.dialog)	{} //dialogs do not effect tab, unless opt.tab is defined.
else if(mode == 'legacy' || mode == 'tabClick'){
	opts.tab = app.ext.admin.u.getTabFromPath(path);
	} //#: denotes to open a tab, but not refresh the content.
else	{
	//hhmm. how did we get here?
	}

if(opts.tab)	{app.ext.admin.u.bringTabIntoFocus(opts.tab);} //changes which tab in the header is in focus.
else	{} //do nothing. perfectly normal to not change what tab is in focus.


//app.u.dump(" -> tab: "+opts.tab);

//set the targetID and $target for the content. 
// By now, tab will be set IF tab is needed. (dialog and/or app mode support no tab specification)
//this is JUST setting targetID. it isn't showing content or opening modals.
if(opts.dialog){
	opts.targetID = 'uiDialog';
	$target = app.ext.admin.u.handleCompatModeDialog(opts); //jquery object is returned by this function.
	}
//load content into whatever tab is specified.
else if(opts.tab)	{
	opts.targetID = opts.tab+"Content";
	$target = $(app.u.jqSelector('#',opts.targetID));
	} 
else if(app.ext.admin.vars.tab)	{
	opts.targetID = app.ext.admin.vars.tab+"Content";
	$target = $(app.u.jqSelector('#',opts.targetID));
	} //load into currently open tab. common for apps.
else	{
	//not in an app. no tab specified. not in modal. odd. how did we get here? No $target will be set. error handling occurs in if($target) check below.
	}


if($target && $target.length)	{
	if(opts.dialog)	{
		app.ext.admin.u.handleShowSection(path,opts,$target); 
		}
	else	{
		app.ext.admin.u.bringTabContentIntoFocus($target); //will make sure $target is visible. if already visible, no harm.
		if(mode == 'app')	{
			app.ext.admin.u.loadNativeApp(path,opts);
			}
		else if(mode == 'legacy')	{
			app.ext.admin.u.handleShowSection(path,opts,$target);
			}
		else if(mode == 'tabClick')	{
//determine whether new content is needed or not. typically, #: is only run from a tab so that when returning to  the tab, the last open content shows up.
			if(opts.tab == app.ext.admin.vars.tab)	{reloadTab = 1; } //tab clicked from a page within that tab. show new content.
			if ($target.children().length === 0)	{ reloadTab = 1; } //no content presently in target. load it.
			if(reloadTab)	{app.ext.admin.u.handleShowSection(path,opts,$target);}
			else	{} //show existing content. content area is already visible thanks to bringTabContentIntoFocus
			}
		else	{}// should never get here. error case for mode not being set is already passed.
		if(opts.tab)	{app.ext.admin.vars.tab = opts.tab;} //do this last so that the previously selected tab can be referenced, if needed.
		}
	}
else	{
	app.u.throwGMessage("Warning! target could not be found or does not exist on the DOM for admin.a.showUI.");
	}
						} //end 'if' for mode.
					else	{
						app.u.throwGMessage("Warning! unable to determine 'mode' in admin.a.showUI. path: "+path);	
						}
					
					}
				else	{
					app.u.throwGMessage("Warning! path not set for admin.a.showUI");
					}
				return false;
				},

/*
A generic form handler. 
$form is a jquery object of the form.
Either _cmd or call must be set in the form data (as hidden, for instance).
 -> _cmd will take the entire serialized form into a dispatch (see note on _tag below).
 -> call should be formatted as extension/call (ex: admin_task/adminTaskUpdate)
If you want to set any _tag attributes, set them as hidden inputs, like so:  <input type='hidden' name='_tag/something' value='someval'> 
 -> these would get formatted as _tag : {'something':'someval'}

Execute your own dispatch. This allows the function to be more versatile
set as onSubmit="app.ext.admin.a.processForm($(this)); app.model.dispatchThis('mutable'); return false;"
 -> if data-q is set to passive or immutable, change the value of dispatchThis to match.
*/
				processForm : function($form,q)	{
					var obj = $form.serializeJSON() || {};
					if($form.length && (obj._cmd || obj.call))	{
//						app.u.dump(" -> admin.a.processForm data attributes: "); app.u.dump(data);
						var _tag = {};
//build the _tag obj.
						for(key in obj)	{
							if(key.substring(0,5) == "_tag/")	{
								_tag[key.substring(5)] = obj[key];//_tag/ must be stripped from key.
								delete obj[key]; //remove from object so it isn't part of query.
								}
							else{}
							}
						app.u.dump(" -> _tag in processForm: "); app.u.dump(_tag);
						if(obj._cmd)	{
							obj._tag = _tag; //when adding straight to Q, _tag should be a param in the cmd object.
							app.model.addDispatchToQ(obj,q);
							}
						else if(obj.call)	{
							var call = obj.call; //save to another var. obj.call needs to be deleted so it isn't passed in dispatch.
							delete obj.call;
							
							app.ext[call.split('/')[0]].calls[call.split('/')[1]].init(obj,_tag,q)
							}
						else{} //can't get here. either cmd or call are set by now.
						
						}
					else	{
						app.u.throwGMessage("Warning! $form was empty or _cmd or call not present within $form in admin.a.processForm");
						}
					}, //processForm
				

//this is a function that brian has in the UI on some buttons.
//it's diferent than showUI so we can add extra functionality if needed.
//the app itself should never use this function.
			navigateTo : function(path,$t)	{
				return this.showUI(path,$t ? $t : {});
				},
				
			showDomainConfig : function(){
				$(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")).empty().showLoading();
				app.ext.admin.calls.adminDomainList.init({'callback':'showDomainConfig','extension':'admin'},'immutable');
				app.model.dispatchThis('immutable')
				},
			
//pass in a domain and an attr
//ex: pass in prt and the partition is returned.
			getDataForDomain : function(domain,attr)	{
				var r = false; //what is returned. will be value, if available.
				var data = app.data['adminDomainList']['@DOMAINS']; //shortcut
				var L = data.length;
				for(var i = 0; i < L; i += 1)	{
					if(data[i].id == domain){
						r = data[i][attr];
						break; //once a match is found, exit.
						}
					else{} //catch.
					}
				return r;
				}, //getDataForDomain

//host is www.zoovy.com.  domain is zoovy.com or m.zoovy.com.  This function wants a domain.
//changeDomain(domain,partition,path). partition and path are optional. If you have the partition, pass it to avoid me looking it up.
			changeDomain : function(domain,partition,path){
				if(domain)	{
					app.vars.domain = domain;
					$('.domain','#appView').text(domain);
//					app.rq.push(['script',0,'http://'+domain+'/jquery/config.js']); //load zGlobals. saves over existing values.
					if(partition){}
					else	{
						partition = app.ext.admin.a.getDataForDomain(domain,'prt');
						}
					$('.partition','#appView').text(partition || "");
	//get entire auth localstorage var (flattened on save, so entire object must be retrieved and saved)
	//something here is causing session to not persist on reload.
					if(app.model.fetchData('authAdminLogin'))	{
						var localVars = app.data['authAdminLogin'];
						localVars.domain = domain;
						localVars.partition = partition || null;
						app.storageFunctions.writeLocal('authAdminLogin',localVars);
						}
					showUI(path || "/biz/setup/index.cgi");
					}
				else	{
					app.u.throwGMessage("WARNING! admin.a.changeDomain required param 'domain' not passed. Yeah, can't change the domain without a domain.");
					}

				}, //changeDomain



//used in the builder for when 'edit' is clicked on an element.
//Params are set by B. This is for legacy support in the UI.

			loadElement : function(type,eleID){
				
				app.ext.admin.calls.adminUIBuilderPanelExecute.init({'sub':'EDIT','id':eleID},{'callback':'showElementEditorHTML','extension':'admin','targetID':'elementEditorContent'});
				app.model.dispatchThis();
				var $editor = $('#elementEditor');
				if($editor.length)	{
					$('#elementEditorMessaging',$editor).empty(); //modal already exists. empty previous content. Currently, one editor at a time.
					$('#elementEditorContent',$editor).empty().addClass('loadingBG');
					} 
				else	{
					$editor = $("<div \/>").attr('id','elementEditor').appendTo('body');
//within the editor, separate messaging/content elements are created so that one can be updated without affecting the other.
//especially impotant on a save where the messaging may get updated and the editor too, and you don't want to nuke the messaging on content update,
// which would happen if only one div was present.
					$editor.append("<div id='elementEditorMessaging'><\/div><div id='elementEditorContent' class='loadingBG'><\/div>").dialog({autoOpen:false,dialog:true, width: 500, height:500,modal:true});
					}
				$editor.dialog('open');
				}, //loadElement


			
//run on a select list inside 'edit' for a product list element.
//various select lists change what other options are available.
//t is 'this' from the select.
//ID is the element ID

			uiProdlistEditorUpdate : function(t,ID)	{
				app.ext.admin.u.uiSaveBuilderElement($("#editorForm"),ID,{'callback':'showMessaging','targetID':'elementEditorMessaging','message':'Saved. Panel updated to reflect new choices.'});
				app.ext.admin.calls.adminUIBuilderPanelExecute.init({'sub':'EDIT','id':ID},{'callback':'showElementEditorHTML','extension':'admin','targetID':'elementEditorContent'});
				app.model.dispatchThis();
				$('#elementEditorContent').empty().append("<div class='loadingBG'><\/div>");
				
				}, //uiProdlistEditorUpdate

/*
to generate an instance of the finder, run: 
app.ext.admin.a.addFinderTo() passing in targetID (the element you want the finder appended to) and path (a cat safe id or list id)

*/
			addFinderTo : function(targetID,findertype,path,attrib)	{
				app.u.dump("BEGIN admin.u.addFinderTo");
				app.u.dump(" -> findertype: "+findertype);
				app.u.dump(" -> path: "+path);
				app.u.dump(" -> attrib: "+attrib);
				if(findertype == 'PRODUCT')	{
					app.ext.store_product.calls.appProductGet.init(path,{"callback":"addPIDFinderToDom","extension":"admin","targetID":targetID,"path":path})
					}
				else if(findertype == 'NAVCAT')	{
//Too many f'ing issues with using a local copy of the cat data.
					app.ext.admin.calls.navcats.appCategoryDetailNoLocal.init(path,{"callback":"addFinderToDom","extension":"admin","targetID":targetID})
					}
				else if(findertype == 'PAGE')	{
					app.ext.admin.calls.appPageGet.init({'PATH':path,'@get':[attrib]},{"attrib":attrib,"path":path,"callback":"addPageFinderToDom","extension":"admin","targetID":targetID})			
					}
				else	{
					app.u.throwGMessage("Warning! Type param for admin.a.addFinderTo is invalid. ["+findertype+"]");
					}
				app.model.dispatchThis();
				}, //addFinderTo
				
//opens a dialog with a list of domains for selection.
//a domain being selected for their UI experience is important, so the request is immutable.
//a domain is necessary so that API knows what data to respond with, including profile and partition specifics.
//though domainChooserDialog is the element that's used, it's passed in the callback anyway for error handling purposes.
			showDomainChooser : function(){
//				app.u.dump("BEGIN admin.a.showDomainChooser");
				$('#domainChooserDialog').dialog('open').showLoading();
				app.ext.admin.calls.adminDomainList.init({'callback':'handleDomainChooser','extension':'admin','targetID':'domainChooserDialog'},'immutable'); 
				app.model.dispatchThis('immutable');
				},	 //showDomainChooser
				
//path - category safe id or product attribute in data-bind format:    product(zoovy:accessory_products)
			showFinderInModal : function(findertype,path,attrib)	{
				var $finderModal = $('#prodFinder');
//a finder has already been opened. empty it.
				if($finderModal.length > 0)	{
					$finderModal.empty();
					}
				else	{
					$finderModal = $('<div \/>').attr({'id':'prodFinder','title':'Product Finder'}).appendTo('body');
					}
//if the finder is for a product attribute, then add a data-sku so we can easily get the sku at any point.
//likewise, if it is NOT for a product, remove the data-pid (which may be present for a previously opened finder) to avoid any confusion down the road.
//data-pid is not used to avoid confusion. it's used on the li items in all the lists to denote which product they contain.
				$finderModal.attr('data-findertype',findertype);
				$finderModal.attr('data-path',path);
				$finderModal.attr('data-attrib',attrib);
				// if (type =='PRODUCT') {
					// var sku = path; 
					// if(sku){$finderModal.attr('data-sku',sku)}
					// else{$finderModal.removeAttr('data-sku')}
					// }
				
				$finderModal.dialog({modal:true,width:'94%',height:650});
				app.ext.admin.a.addFinderTo('prodFinder',findertype,path,attrib);
				}, //showFinderInModal

			login : function($form){
				$('#preloadAndLoginContents').showLoading();
				app.calls.authentication.accountLogin.init($form.serializeJSON(),{'callback':'showHeader','extension':'admin'});
				app.model.dispatchThis('immutable');
				}, //login

			logout : function(){
				$('body').showLoading();
				app.calls.authentication.authAdminLogout.init({'callback':'handleLogout','extension':'admin'});//always immutable.
				app.model.dispatchThis('immutable');
//nuke all this after the request so that the dispatch has the info it needs.
				app.ext.admin.u.selectivelyNukeLocalStorage(); //get rid of most local storage content. This will reduce issues for users with multiple accounts.
				app.model.destroy('authAdminLogin'); //clears this out of memory and local storage. This would get used during the controller init to validate the session.

				} //logout

			}, //action





////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\


		u : {
//executed after preloader if device is logged in.
//executed after login if a login is required.
//If a domain hasn't been selected (from a previous session) then a prompt shows up to choose a domain.
//the entire UI experience revolves around having a domain.
			showHeader : function(){
//				$('#appPreView').hide();
//				$('#appLogin').hide();
				$('#appView').show();
				$('#preloadAndLoginContainer').hide(); //hide all preView and login data.
				$('#preloadAndLoginContents').hideLoading(); //make sure this gets turned off or it will be a layer over the content.
				$('.username','#appView').text(app.vars.username);
				var domain = this.getDomain();
//				app.u.dump(" -> DOMAIN: ["+domain+"]");

//show the domain chooser if one is not set. see showDomainChooser function for more info on why.
//if a domain is already set, this is a return visit. Get the list of domains  passively because they'll be used.
				if (!domain) {
					//the selection of a domain name will load the page content. (but we'll still need to nav)
					app.ext.admin.a.showDomainChooser(); 
					}
				else {
					app.ext.admin.calls.adminDomainList.init({},'passive');
					$('.domain','#appView').text(domain);
// load whatever page is set on the hash onload. The product page being first needs some help.
					var hash = window.location.hash || '/biz/recent.cgi';
					if(hash.substring(0,2) == '#!' || hash.substring(0,2) == '#:')	{}  //app hashes. leave them alone cuz showUI wants #.
					else	{
						hash = hash.replace(/^#/, ''); //strip preceding # from hash.
						}
					app.ext.admin.a.showUI(hash); 
					}
				}, //showHeader


//used to determine what domain should be used. mostly in init, but could be used elsewhere.
			getDomain : function(){
{'fraction_non_alphanumeric': 0.16477562269252768, 'fraction_numerical': 0.0036191565701211587, 'mean_word_length': 3.4031504716549135, 'pattern_counts': {'":': 64, '<': 91, '<?xml version=': 0, '>': 147, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1051324', 'n_tokens_mistral': 36138, 'n_tokens_neox': 30513, 'n_words': 7804}