// ThoughtMesh.js by Jon Ippolito.
// Requires ExpandingMenu.js, telamon.js
// Version 5.0.1, modified by Jon Ippolito to trap for trivial case where document belongs to no submeshes.
/************************* Nomenclature.*************************
GENERAL
	thoughtMesh: the namespace. This and related constants are defined in the loadMesh function.
TAGS
	These are not a property of the menu, so that in the future (?) ThoughtMesh might operate from multiple menus:
	thoughtMesh.uniqueTags: an array of all the tags in the document.
	thoughtMesh.selectedTags: an array defined after user clicks on a tag; this is a manager of objects with integer index and tag name properties, and its contents are updated dynamically.
	thoughtMesh.tagList: a version of thoughtMesh.selectedTags comma-delimited string.
	thoughtMesh.tagsFromItem["print-gift"]: an object of tags associated with that menuItem.
	thoughtMesh.itemsFromTag["art"].menuItems: an object of menuItems tagged with "art".
	thoughtMesh.itemsFromTag["art"].menuItems.length: the number of menuItems tagged with "art". UPGRADE: convert .length to countProperties?
SUBMESHES
	Note that the words submesh and group are used here interchangeably.
	thoughtMesh.submeshes: an object with active submeshes as properties. This doubles as a manager for the groups.
	thoughtMesh.submeshes["group_2"]: an object with properties of a submesh whose database id (not its order in the *thoughtMesh.submeshes* properties!) is 2.
	thoughtMesh.submeshes["group_2"].filter: an object created by the GroupFilter constructor that keeps track of radio buttons.
	thoughtMesh.currentGroup: the submesh selected, referenced by groupId (not its order in the *thoughtMesh.submeshes* properties).
*/
/************************* Utilities.*************************/
function checkSubmeshButton (groupId) {
	if (countProperties( thoughtMesh.submeshes ) > 1) { // ThoughtMesh will only add filters if there is more than one submesh.
		for (var groupCounter in thoughtMesh.submeshes) {
			thoughtMesh.submeshes[groupCounter].filter.input.checked = false ;
		};
		thoughtMesh.submeshes["group_" + groupId].filter.input.checked = true ;
	}
}
function countProperties (argObj) {
	// Non-FF browsers don't support .__count__ property :(
	if (browsers.mz7) {
		return argObj.__count__ ;
	}
	else {
		var numberOfPropertiesInt = 0
		for (prop in argObj) {
			numberOfPropertiesInt++
		}
		return numberOfPropertiesInt ;
	}
}
/************************* Create ThoughtMesh.*************************/
thoughtMesh = new Object() ;
thoughtMesh.defaultOutsideLexiasScript = "http://vectors.usc.edu/thoughtmesh/json/outsideLexias.json.php" ; // Eventually can be localized for different meshes. 
function loadMesh( argObj ) { 
	// argObj looks like { outsideLexiasUrl: 'http://vectors.usc.edu/thoughtmesh/json/outsideLexias.json.php' ,	documentId : 11, method: 'standalone'/'telamon' [outside links via Vectors]/'custom' [handwritten outside lexia JSON], autogenerateInlineNavigation: true/false, primaryGroup: 2 [0 is none] }
	/*__________ Register variables. __________*/
	primaryGroup = (typeof argObj.primaryGroup == "undefined")? 0 : argObj.primaryGroup ;
	thoughtMesh.navigationTopPix = (typeof argObj.navigationTopPix == "undefined")? "20px" : argObj.navigationTopPix ;
	thoughtMesh.navigationMarginBottomPix = (typeof argObj.navigationMarginBottomPix == "undefined")? "16px" : argObj.navigationMarginBottomPix ;
	thoughtMesh.lexiasTopPix = (typeof argObj.lexiasTopPix == "undefined")? "120px" : argObj.lexiasTopPix ;	
	/*________________________ Check browser compatibility.________________________*/
	// Check for browser compliance. If stale browser, go to old browser page. (Safari works for the most part, as is explained in the stale browser page.)
	if ( browsers.ns4 || browsers.ie4 || browsers.ie5 || browsers.ie6) { // Explicit numbers is more future-proof.
		if ( !confirm("Your browser doesn't appear to support all ThoughtMesh features.\n\nClick Cancel to learn more, or\n\nOK to proceed anyway.") ) {
			document.location="recommended_thoughtmesh_browsers.html";
		}
	}
	/*________________________ Decide which ThoughtMesh method to implement.________________________*/
	thoughtMesh.method = (typeof argObj.method == "undefined")? "telamon" : argObj.method ;
	if (thoughtMesh.method == "custom") {
		// Shouldn't have to do anything, because this option means the author will hard-code a custom json file.
	}
	else if (thoughtMesh.method == "standalone") {
		// Prevent JSON errors even though there are no outside lexias for this page.
		document.getElementById("tab-out").style.display = "none" ;
		document.getElementById("tab-trace").style.display = "none" ;
	}
	else { // Telamon.
		// Only load remote data if Internet is present.
		// telamon.offline is set by checking for existence of a ping.js file on the remote server in the HTML head immediately after the telamon.js include.
		if (telamon.offline) {
			addSubmeshStyle( {isOffline: true} ) ;
			document.getElementById("lexias-out-warning").innerHTML = "<span class='warning'>You appear to be offline, so this feature is not available.</span> Please enable Internet and reload this page to use this feature." ;
		}
		else {
			/*__________ Load telamon parameters __________*/
			thoughtMesh.id = ( typeof argObj.documentId != "undefined" )? argObj.documentId :  "[No document id specified!]" ; 
			thoughtMesh.outsideLexiasUrl = ( typeof argObj.outsideLexiasUrl != "undefined" )? argObj.outsideLexiasUrl : thoughtMesh.defaultOutsideLexiasScript ; 
			thoughtMesh.outsideLexiasUrl = encodeURI(thoughtMesh.outsideLexiasUrl) ; // NEC? JB says no.
			debugLoadMessage = 'This script has been loaded via telamon.include: ' + thoughtMesh.outsideLexiasUrl ;
			/*__________ Load all tag JSON. __________*/
			// This checks which tags are common to the entire mesh, so ThoughtMesh can grey out the others when user clicks "excerpts out."
			var scriptEle = document.createElement('script') ;
			scriptEle.type = 'text/javascript' ;
			scriptEle.src = 'http://vectors.usc.edu/thoughtmesh/tech/TagCountAll.js.php' ;
			document.getElementsByTagName('head')[0].appendChild(scriptEle) ;
			/*__________ Initialize submeshes __________*/
			telamon.get( thoughtMesh.outsideLexiasUrl, {documentid: thoughtMesh.id }, "outsideLexiasFun(); initializeSubmeshes() ;" ) ;		
		}
	}
	// Note that inline navigation generation (next, tag, and permalink buttons) takes place in registerTags() so as to make use of predefined tag arrays.
	/*________________________ Initialize tags.________________________*/
	registerTags();
	/*________________________ Initialize tabs.________________________*/
	respondToTab( document.getElementById("tab-here") );
	document.getElementById("loading-shroud").style.display = "none" ;
}
function setTagSize( argObj ) {
	// Don't use cloudTagObjs.length, because you want the final one, not the length at the time each tag is added. Use thoughtMesh.uniqueTags.length instead.
	// Jon used square root to give small numbers a fighting chance. In ems.
	// It doesn't matter how many there are, it matters how big the max is.
	// Goal is for the maximum size to be 4em, minimum .8em. So I want a square root function with f(argObj.maximum) = 4 and f(argObj.minimum) = .8.
	var tagSize = 4 * (   Math.sqrt( .5 * (argObj.frequency/argObj.maximum) )   );
	if (tagSize < 1 ) tagSize = 1 ;
	return tagSize ;
}
function sortTagsNumerically(a,b) {
	return thoughtMesh.itemsFromTag[ a ].length - thoughtMesh.itemsFromTag[ b ].length ;
}
String.prototype.trim = function () {
    return this.replace(/^\s*/, "").replace(/\s*$/, "");
}
function sortAlphabeticallyWithoutCase(a,b) {
	try {
		a = a.toLowerCase(); b = b.toLowerCase();
		if (a>b) return 1 ;
		if (a <b) return -1 ;
		return 0 ;
	}
	catch (e){ // This helps IE handle blanks.
		return 0 ;
	}
}
cloudTagObjs = [] ;
function CloudTag(argObj) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? document.body : argObj.containerEle ;
	this.tag = (typeof argObj.tag == "undefined")? "[no tag defined!]" : argObj.tag ;
	this.frequency = (typeof argObj.frequency == "undefined")? "[no frequency defined!]" : argObj.frequency ; // Not needed immediately, but could be useful later.
	this.size = (typeof argObj.size == "undefined")? "[no size defined!]" : argObj.size ;
	// Note: textNodes are weird, don't seem to afford same handles as elements. Hence the element repetition instead of merely replacing textNode + with -.
	/* _______ Create a box for holding +,-, and tag. _______*/
	this.tagBox = document.createElement("span") ;
	this.tagBox.className = "tag-box" ;
	this.containerEle.appendChild( this.tagBox ) ;
	/* _______ Create a blank space-holder for plus and minus links. _______*/
	this.blankLink = document.createElement("span") ;
	this.blankLink.className = "blank-link" ;
	this.blankLink.innerHTML = "+" ;
	this.blankLink.style.fontSize = this.size + "em" ;
	this.tagBox.appendChild( this.blankLink ) ;
	/* _______ Create a link for adding a tag. _______*/
	this.plusLink = document.createElement("a") ;
	this.plusLink.href = "#" ;
	this.plusLink.style.fontSize = this.size + "em" ;
	this.plusLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "plus"}) } ;
	this.plusLink.className = "cloud-tag-hidden" ;
	this.plusText =  document.createTextNode("+") ;
	// Add a means of recovering the corresponding tag from the link, for use in the onclick function.
	this.plusLink.cloudTagObj = this ;
	// Add plus to cloud.
	this.tagBox.appendChild( this.plusLink ) ;
	this.plusLink.appendChild( this.plusText ) ;
	/* _______ Create a link for subtracting a tag. _______*/
	this.minusLink = document.createElement("a") ;
	this.minusLink.href = "#" ;
	this.minusLink.style.fontSize = this.size + "em" ;
	this.minusLink.className = "cloud-tag-hidden" ; // Initially hidden.
	this.minusLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "minus"}) } ;
	this.minusText = document.createTextNode("-") ;
	// Add a means of recovering the corresponding tag from the link, for use in the onclick function.
	this.minusLink.cloudTagObj = this ;
	// Add minus to cloud.
	this.tagBox.appendChild( this.minusLink ) ;
	this.minusLink.appendChild( this.minusText ) ;
	/* _______ Create a link for replacing a tag. _______*/
	this.replaceLink = document.createElement("a") ;
	this.replaceLink.href = "#" ;
	this.replaceLink.style.fontSize = this.size + "em" ;
	this.replaceLink.onclick = function() { respondToTag({tagArg: this.cloudTagObj.tag, linkType: "replace"}) } ;
	this.replaceLink.className = "cloud-tag" ;
	this.replaceText =  document.createTextNode( this.tag ) ;
	// Enable recovery of cloudTagObj from its link, for use in the onclick function.
	this.replaceLink.cloudTagObj = this ;
	// Add replace link to cloud.
	this.tagBox.appendChild(this.replaceLink) ;
	this.replaceLink.appendChild( this.replaceText ) ;
	/* _______ Add a space to separate tags. _______*/
	this.containerEle.appendChild(document.createTextNode(" ")) ;
}
function registerTags() {
	// Add tag properties to dynamic menu.
	thoughtMesh.selectedTags = new Array() ; // Used after clicking on a tag, this is a manager of objects with integer index and tag name properties.
	thoughtMesh.tagsFromItem = new Object() ; // This cannot be accessed as an array.
	thoughtMesh.itemsFromTag = new Object() ; // Ditto.
	thoughtMesh.uniqueTags = new Array() ;
	/*__________ Loop through all items in menu, registering tags and adding inline navigation. __________*/
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		// Check whether this item has any tags. Introduction, notes, or superlinks may not.
		if ( homeMenu.itemManager[itemCounter].htmlElement.getAttribute("tags") != null) {
			thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ] = homeMenu.itemManager[itemCounter].htmlElement.getAttribute("tags").split(",") ;
			// Loop through all tags for that item.
			for (var newTagCounter=0; newTagCounter < thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ].length; newTagCounter++) {
				var thisTag = thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ][newTagCounter].trim() ; // Tag as string, with whitespace removed.
				thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ][newTagCounter] = thisTag ; // Add trimmed tag back into tagsFromItem array for future use.
				if ( typeof thoughtMesh.itemsFromTag[ thisTag ] == "undefined") { // This tag has not yet been registered.
					thoughtMesh.itemsFromTag[ thisTag ] = new Array() ;
					thoughtMesh.uniqueTags.push( thisTag ) ;
				}
				thoughtMesh.itemsFromTag[ thisTag ].push( homeMenu.itemManager[itemCounter] ) ;
			}; // End loop through tags for that item.
			// Add navigation to top and bottom of content div if requested.
			if ( homeMenu.autogenerateInlineNavigation == "none" || (homeMenu.autogenerateInlineNavigation == "allButFirst" && itemCounter==0) ) {
				// Don't add navigation.
			}
			else {
				addInlineNavigation({ menuItem: homeMenu.itemManager[itemCounter], menuEleId: homeMenu.htmlElement.id, hasTags: true }) ;
			}
		} // End check for tag existence.
		else {
			// If there are no tags, give it a default (empty) array; it will report length = 0.
			thoughtMesh.tagsFromItem[ homeMenu.itemManager[itemCounter].htmlElement.id ] = new Array() ;
			// Add navigation to top and bottom of content div if requested.
			if ( homeMenu.autogenerateInlineNavigation == "none" || (homeMenu.autogenerateInlineNavigation == "allButFirst" && itemCounter==0) ) {
				// Don't add navigation.
			}
			else {
				addInlineNavigation({ menuItem: homeMenu.itemManager[itemCounter], menuEleId: homeMenu.htmlElement.id, hasTags: false }) ;
			}
		}
	}; // End loop through items.
	// First sort unique tags by frequency to get max and min values for normalizing tag size.
	thoughtMesh.uniqueTags.sort( sortTagsNumerically ) ;
	thoughtMesh.tagFrequencyMin = 0;
	thoughtMesh.tagFrequencyMax = 0;
	if (typeof(thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[0] ]) != 'undefined') {
	  thoughtMesh.tagFrequencyMin = thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[0] ].length;
	  thoughtMesh.tagFrequencyMax = thoughtMesh.itemsFromTag[  thoughtMesh.uniqueTags[ thoughtMesh.uniqueTags.length-1 ]  ].length ;
	}
	// Now sort unique tags alphabetically (default).
	thoughtMesh.uniqueTags.sort( sortAlphabeticallyWithoutCase ) ;
	// Now add tags to cloud.
	for (var tagCounter=0; tagCounter < thoughtMesh.uniqueTags.length; tagCounter++) {
		var thisTagFrequency = thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[tagCounter] ].length ;
		var thisCloudTag = new CloudTag({
			containerEle: document.getElementById("home-cloud"),
			tag: thoughtMesh.uniqueTags[tagCounter],
			frequency: thoughtMesh.itemsFromTag[ thoughtMesh.uniqueTags[tagCounter] ].length,
			size: setTagSize( {
				frequency:thisTagFrequency, 
				minimum:thoughtMesh.tagFrequencyMin, 
				maximum:thoughtMesh.tagFrequencyMax
			})
		}) ;
		cloudTagObjs.push( thisCloudTag ) ;
		// Enable recovery of cloudTagObj from its name, for use in style highlighting--eg, cloudTagObjs["technology"].
		cloudTagObjs[ thoughtMesh.uniqueTags[tagCounter] ] = thisCloudTag ;
	}
}
function DocumentCitation(argObj) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? "[no container Element defined]" : argObj.containerEle ;
	this.author = (typeof argObj.author == "undefined")? "[no author defined]" : argObj.author ;
	this.title = (typeof argObj.title == "undefined")? "[no title defined]" : argObj.title ;
	this.authorAndTitle = document.createElement("div") ;
	this.authorAndTitle.className = "author-and-title" ;
	this.authorAndTitle.innerHTML = this.author + ", &quot;" + this.title + "&quot; " ;
	this.containerEle.appendChild( this.authorAndTitle ) ;
}
function LexiaExcerpt( argObj ) {
	// Register arguments.
	this.containerEle = (typeof argObj.containerEle == "undefined")? "[no container Element defined]" : argObj.containerEle ;
	this.headingText =  (typeof argObj.headingText == "undefined")? "[no heading text defined]" : argObj.headingText ;
	this.isOutside = (typeof argObj.isOutside == "undefined" ||  !argObj.isOutside )? false : true ;
	this.itemEle = (typeof argObj.itemEle == "undefined")? "[no item element defined]" : argObj.itemEle ; // A kludge, but it should throw an illuminating error.
	this.lexiaText = (typeof argObj.lexiaText == "undefined")? "[no lexia text defined]" : argObj.lexiaText ;
	// Create the lexia div.
	this.excerptDiv = document.createElement("div") ;
	this.containerEle.appendChild( this.excerptDiv ) ;
	this.excerptDiv.className = "lexia-excerpt" ;
	// Add a means of recovering the corresponding menu item from the div, for use in the onclick function.
	this.excerptDiv.lexiaObj = this ;
	// Create the heading link.
	this.headingLink = document.createElement("a") ;
	this.excerptDiv.appendChild( this.headingLink ) ;
	this.headingLink.appendChild( document.createTextNode(this.headingText) ) ;
	if( this.isOutside ) {
		// Start with url passed by ajax data on related outside lexias.
		this.headingLink.referredUrl =  (typeof argObj.url == "undefined")? "[no url defined]" : argObj.url ;
		this.headingLink.referredAnchor =  (typeof argObj.anchor == "undefined")? "[no anchor defined]" : argObj.anchor ;
		// Then add any tags currently selected, so remote ThoughtMesh will activate them too.
		var selectedTagsVar = "" ;
		if ( thoughtMesh.selectedTags.length > 0 ) { // User has selected some tags.
			selectedTagsVar += thoughtMesh.selectedTags[0].tag
			for (var tagObjCounter=1; tagObjCounter < thoughtMesh.selectedTags.length; tagObjCounter++) {
				selectedTagsVar += "&" + thoughtMesh.selectedTags[tagObjCounter].tag
			};
			this.headingLink.referredUrl += "?" + selectedTagsVar + "#" + this.headingLink.referredAnchor ;	
		};
		this.headingLink.href =  this.headingLink.referredUrl ; // UPGRADE: Check for existence?
	}
	else {
		this.headingLink.href = "#" ;
		this.headingLink.onclick = function() { respondAsIf( this.parentNode.lexiaObj.itemEle ) } ;
	}
	// Not using w3c method enables highlighting inside search returns :(
	// this.excerptDiv.appendChild( document.createTextNode(this.lexiaText) ) ;
	this.lexiaHTML = document.createElement("div") ;
	this.lexiaHTML.innerHTML = this.lexiaText ;
	this.excerptDiv.appendChild( this.lexiaHTML ) ;
}
lexiaExcerptObjs = [] ;
lexiaExcerptOutObjs = [] ;
tabs = [] ;
function Tab( argObj ) {
	// UPGRADE: build DOM tabs here too?
	tabs.push( argObj ) ;
	tabs[ argObj.id ] = argObj ;
}
new Tab( {id: "tab-here", presentationId: "lexias-here", isShowing: true } ) ;
new Tab( {id: "tab-out", presentationId: "lexias-out", isShowing: true } ) ;
new Tab( {id: "tab-search", presentationId: "search-here", isShowing: true } ) ;
new Tab( {id: "tab-trace", presentationId: "trace", isShowing: true } ) ;
/************************* Manage submeshes (groups).*************************/
function initializeSubmeshes () {
	// This function is always called after the first Telamon call in loadMesh, even if a document does not belong to a submesh.
	// documentGroups and primaryGroup are defined in JSON delivered in the initial Telamon call at the end of loadMesh function.
	thoughtMesh.submeshes = ( typeof documentGroups != "undefined" )? documentGroups :  "[No submeshes specified!]" ; // Specifies which submeshes this essay belongs to--defined in HTML document.
	// UPGRADE: REMOVE AFTER CRAIG FIXES JSON BUG: 
	thoughtMesh.currentGroup = primaryGroup ; // This is the default, consisting of all submeshes. Clicking on a radio button in the "outside lexias" tab will change this.
	/*__________ Create buttons to filter for each submesh this essay belongs to. __________*/
	if ( countProperties( thoughtMesh.submeshes ) > 1 ) { 	// All documents should have default group of 0, which is all submeshes. submeshes is passed as an object, so can't use .length
		for (var group in thoughtMesh.submeshes) {
			new GroupFilter(thoughtMesh.submeshes[ group ]) ;
			// THIS REPLACED THE FOLLOWING DEFINITION OF primaryGroup--though no existence check for primaryGroup yet. thoughtMesh.currentGroup = thoughtMesh.submeshes[ group ].groupId ; // This will default submesh selection to the last submesh in the thoughtMesh.submeshes object (if it were an array it would be more flexible).
		};
	}
	checkSubmeshButton(thoughtMesh.currentGroup) ;
	/*__________ Load remote (up-to-date) submesh style and images, if appropriate. __________*/
	// Note: the choice of the submesh's open and closed triangles (green, red, etc) is defined in the Mesh stylesheet, and the actual images are defined online.
	if ( primaryGroup > 0 ) {
		addSubmeshStyle( {isOffline: false} ) ;
	}
}
function addSubmeshStyle( argObj ) {
	var isOffline = (typeof argObj.isOffline == "undefined")? telamon.offline : argObj.isOffline ;
	// primaryGroup is an integer defined in the telamon call in loadMesh.
	var submeshPath = (isOffline)? "uploaded/group_" + primaryGroup + "/" : "http://thoughtmesh.net/publish/uploaded/group_" + primaryGroup + "/" ;
	var submeshLogoURL = (isOffline)? "uploaded/group_" + primaryGroup + "/logo.png" : documentGroups["group_" + primaryGroup].groupImage ;
	var submeshBackgroundURL = (isOffline)? "uploaded/group_" + primaryGroup + "/background.jpg" : documentGroups["group_" + primaryGroup].groupBackgroundImage ;
	// Add submesh stylesheet.
	var submeshStylesheetLink = document.createElement("link") ;
	submeshStylesheetLink.type = "text/css" ;
	submeshStylesheetLink.rel = "stylesheet" ;
	submeshStylesheetLink.href = submeshPath + "default.css" ;
	submeshStylesheetLink.media = "screen" ;
	document.getElementsByTagName("head")[0].appendChild(submeshStylesheetLink) ;
	// UPGRADE: check for existence of these files before adding them.
	// Add submesh background (watermark) image. So as not to overwrite a body background color, this is done in parts. Note different file extensions.
	document.body.style.backgroundImage = "url(" + submeshBackgroundURL + ")" ;
	document.body.style.backgroundRepeat = "repeat-x" ;
	// Add submesh logo image. (Not added as background to div, because that seems to screw up transparent logos.)
	var submeshLogoImage = document.createElement("img") ;
	submeshLogoImage.src = submeshLogoURL ;
	document.getElementById("logo").appendChild(submeshLogoImage) ;
	/*__________ Adjust layout to make room for mesh skin images. __________*/
	document.getElementById("navigation").style.top = thoughtMesh.navigationTopPix ;
	document.getElementById("navigation").style.marginBottom = thoughtMesh.navigationMarginBottomPix ;
	document.getElementById("lexias").style.top = thoughtMesh.lexiasTopPix ;
}
function showOutsideLexiasForSubmesh () {
	if (telamon.offline) {
		document.getElementById("lexias-out-warning").innerHTML = "<span class='warning'>You appear to be offline, so this feature is not available.</span> Please enable Internet and reload this page to use this feature." ;
	}
	else {		
    var pg = (typeof(thoughtMesh.currentGroup) != 'undefined') ? thoughtMesh.currentGroup : 0;
		telamon.get( thoughtMesh.outsideLexiasUrl, {tag: thoughtMesh.tagList, documentid: thoughtMesh.id, groupid: pg }, "outsideLexiasFun(); showOutsideLexias();" ); // tagList is a comma-delimited string.
	}
}
function filterSubmesh (submeshInputEle) {
	// Show only articles that match the submesh chosen by the user.
	thoughtMesh.currentGroup = submeshInputEle.value ;
	document.getElementById("lexias-out-listing").innerHTML = "" ;
	// Only get outside lexias if a tag has been chosen.
	if (thoughtMesh.selectedTags.length > 0) {
		showOutsideLexiasForSubmesh() ;
	}
	// Reset checkboxes.
	checkSubmeshButton(submeshInputEle.value) ;
}
function GroupFilter( groupObj ) {
	/*__________ Add this filter to the groupObj that is part of thoughtMesh.submeshes. __________*/
	groupObj.filter = this ;
	/*__________ Register arguments. __________*/
	this.id = (typeof groupObj.groupId == "undefined")? "[no group id defined!]" : groupObj.groupId ; // An integer; zero is "all submeshes".
	this.name = (typeof groupObj.groupName == "undefined")? "[no group name defined!]" : groupObj.groupName ;
	this.url = (typeof groupObj.groupUrl == "undefined")? "[no group url defined!]" : groupObj.groupUrl ;
	/*__________ Create elements and define their parameters. __________*/
	this.groupFilterDiv = document.createElement("div") ;
	this.input = document.createElement("input") ;
	this.input.type = "radio" ;
	this.input.value = this.id ;
	this.input.onclick = function() { filterSubmesh( this ) } ;
	this.text = document.createTextNode( this.name + " ") ;
	this.link = document.createElement("a") ;
	this.linkText = document.createTextNode( "[link]" ) ;
	this.link.href = this.url ;
	this.link.target = "_blank" ;
	this.link.className = "submesh-home-link" ;
	/*__________ Append newly created elements. __________*/
	document.getElementById("submesh-form").appendChild( this.groupFilterDiv ) ;
	this.groupFilterDiv.appendChild( this.input ) ;
	this.groupFilterDiv.appendChild( this.text ) ;
	this.groupFilterDiv.appendChild( this.link ) ;
	this.link.appendChild( this.linkText ) ;
}
/************************* Respond to user choices.*************************/
function respondToTag( argObj ) {
	/*________________________ Prepare.________________________*/
	// Register some variables.
	var tagArg =  (typeof argObj.tagArg == "undefined")? "[no tagArg passed!]" : argObj.tagArg ;
	var linkType = (typeof argObj.linkType == "undefined")? "[no linkType passed]" : argObj.linkType ;
	// Focus correct tab.
	if ( !tabs["tab-out"].isShowing ) {
		respondToTab( document.getElementById("tab-here") ) ;
	}
	// Clear out old lexias.
	document.getElementById("lexias-here").innerHTML = "" ;
	document.getElementById("lexias-out-listing").innerHTML = "" ;
	lexiaExcerptObjs = [] ;
	/*________________________ Adjust tag styles.________________________*/
	switch (linkType) {
		case "replace": // Replacing old tags with a single new tag.
			// Reset all styles.
			for (var cloudTagCounter=0; cloudTagCounter < cloudTagObjs.length; cloudTagCounter++) {
				cloudTagObjs[cloudTagCounter].replaceLink.className = "cloud-tag" ;
				cloudTagObjs[cloudTagCounter].plusLink.style.display = "inline" ;
				cloudTagObjs[cloudTagCounter].minusLink.className = "cloud-tag" ;
				cloudTagObjs[cloudTagCounter].minusLink.style.display = "none" ;
				try {
				cloudTagObjs[cloudTagCounter].blankLink.style.display = "none" ;
				}
				catch(e) {
				}
			}
			// Style this tag.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].plusLink.style.display = "none" ;
			cloudTagObjs[tagArg].minusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			// If starting over from a new tag, wipe out the old one.
			thoughtMesh.selectedTags = [] ;
			thoughtMesh.selectedTags.push( {tag: tagArg, integer: 0} ) ; // thoughtMesh.selectedTags is a manager of objects with integer index and tag name properties.
			thoughtMesh.selectedTags[tagArg] = thoughtMesh.selectedTags[0] ;
			break ;
		case "plus": // Adding a tag in combination.
			// Register new tag. Currently no limitation on number, but they always intersect.
			thoughtMesh.selectedTags.push( {tag: tagArg, integer: thoughtMesh.selectedTags.length} ) ;
			// Enable recovery of array index from the tag name, for use in subtracting tag below.
			thoughtMesh.selectedTags[tagArg] = thoughtMesh.selectedTags[ thoughtMesh.selectedTags.length-1 ] ; // This is an object.
			// Style this tag.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].plusLink.style.display = "none" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].minusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].blankLink.style.display = "none" ;
			break ;
		case "minus": // Subtracting a tag in combination.
			thoughtMesh.selectedTags.splice( thoughtMesh.selectedTags[tagArg].integer, 1 ) ;
			// The above changes the .integer property of each subsequent tag, so pop the rest of them up to compensate.
			for (var remainingTagCounter=thoughtMesh.selectedTags[tagArg].integer; remainingTagCounter < thoughtMesh.selectedTags.length; remainingTagCounter++) {
				thoughtMesh.selectedTags[remainingTagCounter].integer = remainingTagCounter ;
			};
			// Reset this tag to normal styles.
			cloudTagObjs[tagArg].replaceLink.className = "cloud-tag" ;
			cloudTagObjs[tagArg].plusLink.style.display = "inline" ;
			cloudTagObjs[tagArg].minusLink.className = "cloud-tag-selected" ;
			cloudTagObjs[tagArg].minusLink.style.display = "none" ;
			cloudTagObjs[tagArg].blankLink.style.display = "none" ;
			break ;
	}
	// Hide content.
	document.getElementById('content').style.display="none" ;
	/*________________________ Identify and show matching lexias in.________________________*/
	// Loop through menu items.
	nextItemLabel:
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		var thisItemEle = homeMenu.itemManager[itemCounter].htmlElement ;
		if ( thoughtMesh.tagsFromItem[ thisItemEle.id ].length == 0 ) { // Eliminates menu items without tags.
			continue nextItemLabel ; // Skips to next menu item.
		}
		else {
			homeMenu.itemManager[itemCounter].selectedViaTags = false ; // Temporary assignment, usually contradicted below.
			// Loop through the selected tags in the cloud.
			for (var cloudTagCounter=0; cloudTagCounter < thoughtMesh.selectedTags.length; cloudTagCounter++) {
				homeMenu.itemManager[itemCounter].selectedViaTags = false ;
				// Loop through that item's tags.
				for (var itemTagCounter=0; itemTagCounter < thoughtMesh.tagsFromItem[ thisItemEle.id ].length; itemTagCounter++) {
					// If an item's tag does not match any one of the selected tags, mark it for non-highlight.
					if ( thoughtMesh.tagsFromItem[ thisItemEle.id ][itemTagCounter] == thoughtMesh.selectedTags[cloudTagCounter].tag ) {
						// A match!
						homeMenu.itemManager[itemCounter].selectedViaTags = true ;
					}
					else {
						// Continue with false.
					}
				}
				if ( !homeMenu.itemManager[itemCounter].selectedViaTags ) {
					continue nextItemLabel ;
				}
			}
			if (homeMenu.itemManager[itemCounter].selectedViaTags ) {
				// Strip out HTML.
				var lexiaTextClean = getInnerText( homeMenu.itemManager[itemCounter].content.innerHTML ) ;
				// Strip out "next Lexia" link.
				if (lexiaTextClean.indexOf("Next &gt;") != -1) {
					var startLexia =  lexiaTextClean.indexOf("Next &gt;") + 10 ; // Add number of characters necessary to go past initial link.
				}
				else {
					var startLexia =  0 ;
				}
				// JS trivia: substring takes end point as second argument; substr takes number of characters to include.
				lexiaTextClean = lexiaTextClean.substr( startLexia, 300 )  + "..." ;
				var lexiaExcerptObj = new LexiaExcerpt( {
					containerEle: document.getElementById("lexias-here"),
					headingText: thisItemEle.innerHTML,
					itemEle: thisItemEle,
					lexiaText: lexiaTextClean
				} ) ;
				// Add to excerpt manager.
				lexiaExcerptObjs.push(lexiaExcerptObj) ;
				// Create a handle that looks like lexiaExcerptsObj["introduction"].
				lexiaExcerptObjs[lexiaExcerptObj.itemEle.id] = lexiaExcerptObj ;
			}
		}					
	}
	if (lexiaExcerptObjs.length == 0 ) {
		warnNoMatch( { tagArray: thoughtMesh.selectedTags, presentationId: "lexias-here" } ) ;
	}
	/*________________________ Identify and show matching lexias out.________________________*/
	if ( typeof thoughtMesh.selectedTags[0] != "undefined") {
		thoughtMesh.tagList = thoughtMesh.selectedTags[0].tag ;
		for (var tagCounter=1; tagCounter < thoughtMesh.selectedTags.length; tagCounter++) {
			thoughtMesh.tagList += "," + thoughtMesh.selectedTags[tagCounter].tag ;
		};
		if( thoughtMesh.method == "telamon") {
			if (telamon.offline) {
				document.getElementById("lexias-out-warning").innerHTML = "<span class='warning'>You appear to be offline, so this feature is not available.</span> Please enable Internet and reload this page to use this feature." ;
			}
			else {
				showOutsideLexiasForSubmesh() ;
			}
		}
		if( thoughtMesh.method == "custom") {
			outsideLexiasFun() ;
			showOutsideLexias() ;
		}
		if( thoughtMesh.method == "standalone") {
			document.getElementById("lexias-out-warning").innerHTML = "<span class='warning'>This page has no outside excerpts.</span>" ; // Default--move?
		}
	}
	else {
		document.getElementById("lexias-out-warning").innerHTML = "Pick a tag to see related excerpts." ;
		document.getElementById("lexias-here").innerHTML = "Pick a tag to see related excerpts." ;
		// Make plusses go away.
		for (var cloudTag in cloudTagObjs) {
			cloudTagObjs[cloudTag].plusLink.style.display = "none" ;
			cloudTagObjs[cloudTag].blankLink.style.display = "inline" ;
		}
	}
};
function respondToTagInContent( linkEle ) {
	respondToTag({tagArg: linkEle.firstChild.nodeValue , linkType: 'replace'}); 
	hideCloud({ wantClosed: false }) ;
}
function hideCloud(cloudHeaderEle, wantClosed) { // DEPRECATED: cloudHeaderEle is no longer necessary; could be useful in future if more than one cloud?
	if (wantClosed) {
		document.getElementById("thoughtmesh-title-id").className = "thoughtmesh-title-closed" ;
		document.getElementById("cloud-container").style.display = "none" ;		
		cloudHeaderEle.onmouseover = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title'} ;
		cloudHeaderEle.onmouseout = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title-closed'} ;

	}
	else { // You want it open.
		document.getElementById("thoughtmesh-title-id").className = "thoughtmesh-title" ;
		document.getElementById("cloud-container").style.display = "block" ;
		cloudHeaderEle.onmouseover = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title-closed'} ;
		cloudHeaderEle.onmouseout = function() {this.getElementsByTagName("div")[0].className = 'thoughtmesh-title'} ;
	}
}
function toggleCloud(cloudHeaderEle) {
	if ( document.getElementById("cloud-container").style.display != "none" ) { // Cloud is open (true on load).
		hideCloud(cloudHeaderEle, true) ;
	}
	else { // Cloud is closed.
		hideCloud(cloudHeaderEle, false) ;
	}
}
function respondToTab(tabEle) {
	// Reset all tabs and content to "off".
	for (var tabCounter=0; tabCounter < document.getElementById("tabs").getElementsByTagName("div").length; tabCounter++) {
		document.getElementById("tabs").getElementsByTagName("div")[tabCounter].className = "tab" ; // Default
		document.getElementById("tabs").getElementsByTagName("div")[tabCounter].style.borderBottomWidth = "0px" ;
	};
	for (var tabCounter=0; tabCounter < tabs.length; tabCounter++) {
		tabs[tabCounter].isShowing = false ;
		document.getElementById( tabs[tabCounter].presentationId ).style.display = "none" ;		
	};
	// Highlight chosen content.
	tabEle.style.borderBottomWidth = "2px" ;
	tabs[tabEle.id].isShowing = true ;
	document.getElementById( tabs[tabEle.id].presentationId ).style.display = "block" ;
	// For outside lexias, fade tags to show which tags actually correspond to outside lexias in the mesh.
	for (var i=0; i < thoughtMesh.uniqueTags.length; i++) {
		if (tabEle.id == "tab-out") { // User wants to see outside lexias.
			if ( typeof numTaggedWith != "undefined") { // Make sure user is online.
				if ( numTaggedWith.documents[ thoughtMesh.uniqueTags[i] ] > 1 ) { // There are other documents with that tag.
					// Leave opaque.
				}
				else {
					cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.style.opacity = ".2" ;
					// cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.filters.item("DXImageTransform.Microsoft.Alpha").Opacity=20 ;
					// cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.filters["DXImageTransform.Microsoft.Alpha"].Opacity=20 ;
					if ( browsers.ie ) { // God-damned IE fails to style tagBox or apply opacity filter >:0
						cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontWeight = "normal" ;			
						cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontStyle = "italic" ;			
					};
				}
			}
		}
		else { // User chose a different tab than outside lexias.
			cloudTagObjs[ thoughtMesh.uniqueTags[i] ].tagBox.style.opacity = "1" ;			
			if ( browsers.ie ) { // God-damned IE fails to style tagBox or apply opacity filter >:0
				cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontWeight = "bold" ; // KLUDGE. Damned IE!			
				cloudTagObjs[ thoughtMesh.uniqueTags[i] ].replaceLink.style.fontStyle = "normal" ; // KLUDGE.		
			};
		}
	};
}
function showOutsideLexias() { // thoughtMesh.selectedTags is a manager with a "tag" property.
	if (thoughtMesh.selectedTags[0]) {
		// Loop through documents in outsideLexiasObj.
		var outsideLexiasFound = false ;
		for (var documentProperty in outsideLexiasObj){
			outsideLexiasFound = true ;
			var documentCitation = new DocumentCitation( {
				containerEle: document.getElementById("lexias-out-listing"),
				author: outsideLexiasObj[documentProperty].author,
				title: outsideLexiasObj[documentProperty].title,
				url: outsideLexiasObj[documentProperty].url
			} ) ;
			// Loop through lexias in that document.
			for ( var lexiaProperty in outsideLexiasObj[documentProperty].lexias ) {
				var lexiaExcerptOutObj = new LexiaExcerpt( {
					containerEle: document.getElementById("lexias-out-listing"),
					url: outsideLexiasObj[documentProperty].url,
					headingText: outsideLexiasObj[documentProperty].lexias[lexiaProperty].heading,
					anchor: outsideLexiasObj[documentProperty].lexias[lexiaProperty].anchor,
					isOutside: true,
					lexiaText: outsideLexiasObj[documentProperty].lexias[lexiaProperty].excerpt
				} ) ;
				// Add to excerpt manager.
				lexiaExcerptOutObjs.push(lexiaExcerptOutObj) ;
				// Create a handle that looks like lexiaExcerptsObj["introduction"]. UPGRADE: NOT SURE IF THIS IS NEC.
				lexiaExcerptOutObjs[lexiaExcerptOutObj.itemEle.id] = lexiaExcerptOutObj ;
			}
		}
	}
	else {
		document.getElementById("lexias-out-warning").innerHTML = "Pick a tag to see related excerpts." ; // Default--move?
	}
	if ( !outsideLexiasFound ) {
		warnNoMatch( { tagArray: thoughtMesh.selectedTags, presentationId: "lexias-out-listing" } ) ;
	}
}
/************************* Other utilities.*************************/
function warnNoMatch( argObj ) {
		if (argObj.presentationId == "search-here") {
			var warningHTML =  "<h3> <span class='warning'>No excerpts contain that character combination.</span></h3>" ;
			document.getElementById("search-warning").innerHTML = warningHTML ;
		}
		else { // A tag combination.
			var tagList = "";
			for (var tagCounter=0; tagCounter < argObj.tagArray.length; tagCounter++) {
				tagList += argObj.tagArray[tagCounter].tag + " ";
			};
			var warningHTML =  "<h3>No excerpts match the tag combination <span class='warning'>" ;
			warningHTML += tagList ;
			warningHTML += ".</span></h3>" ;
			document.getElementById(argObj.presentationId).innerHTML = warningHTML ;
		}
}
function solicitHelp() {
	if ( confirm("ThoughtMesh is tag-based navigation for Web pages.\n\nCancel if you want to continue navigating this page.\n\nOr click OK to learn more at ThoughtMesh.net.") ) {
		document.location="http://vectors.usc.edu/thoughtmesh/help/";
	}
}
var htmlTagRegExp = /<\/?[^>]+>/gi;
function getInnerText(htmlArg){ // Because .innerText only works in IE.
	// Strip out HTML tags.
	htmlArg = htmlArg.replace(htmlTagRegExp,"");
	return htmlArg;
}
function searchFilter(searchString) { // Written to be independent of tag-based lexia navigation.
	/*________________________ Wait for three characters.________________________*/
	if (searchString.length < 3) {
		document.getElementById("search-warning").innerHTML = "Please type at least three characters." ;		
	}
	else { // Proceed with search.
		/*________________________ Initialize some objects.________________________*/
		var regexObj = new RegExp(searchString, "i");
		regexObj.compile(searchString, "i");
		lexiaSearchedObjs = [] ;
		document.getElementById("search-warning").innerHTML = "" ;
		document.getElementById("search-results").innerHTML = "" ;
		/*________________________ If content contains string, add a lexia to search presentation.________________________*/
		for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
			var contentText = getInnerText( homeMenu.itemManager[itemCounter].content.innerHTML ) ;
			if ( regexObj.test(contentText) ) {
				// Find the first place where the string appears. Pick the smaller of the lowercase (default) and capitalized appearances.
				var firstStringOccurrence = contentText.indexOf( searchString ) ;
				var firstCapitalizedOccurrence = contentText.indexOf( searchString.substr(0,1).toUpperCase() + searchString.substr( 1, searchString.length-1 ) ) ;
				if ( firstCapitalizedOccurrence >= 0 ) { // A capital was found.
					if ( firstStringOccurrence < 0 || firstStringOccurrence > firstCapitalizedOccurrence ) { // Lowercase doesn't appear or is after capitalized appearance.
						firstStringOccurrence = firstCapitalizedOccurrence ;
					}
				}
				if (firstStringOccurrence > 20) { // So you don't ask for more characters at the beginning than you have.
				// substring takes as second arg the *position* you want to go from the beginning.
				// substr takes as second arg the *length* you want.
					var contentToDisplay = "..." + contentText.substring( firstStringOccurrence-20 , firstStringOccurrence ) ;
				}
				else { // String appears near the beginning, so start there.
					var contentToDisplay = contentText.substring( 0 , firstStringOccurrence ) ;
				}
				// Now add and highlight the word found.
				contentToDisplay += "<strong>" + contentText.substr( firstStringOccurrence, searchString.length ) + "</strong>" ;
				// Now add the rest of the excerpt, to a total of 300 characters. Subtract 17 to account for <strong></strong>.
				contentToDisplay += contentText.substr(  firstStringOccurrence + searchString.length , 300 - (contentToDisplay.length - 17) ) + "...";
				var lexiaExcerptObj = new LexiaExcerpt( {
					containerEle: document.getElementById("search-results"),
					headingText: homeMenu.itemManager[itemCounter].htmlElement.innerHTML,
					itemEle: homeMenu.itemManager[itemCounter].htmlElement,
					lexiaText: contentToDisplay
				} ) ;
				// Add to excerpt manager.
				lexiaSearchedObjs.push(lexiaExcerptObj) ;
				// Create a handle that looks like lexiaSearchedObjs["introduction"].
				lexiaSearchedObjs[lexiaExcerptObj.itemEle.id] = lexiaSearchedObjs ;
			}
		};	
		if (lexiaSearchedObjs.length == 0 ) {
			warnNoMatch({ tagArray: thoughtMesh.selectedTags, presentationId: "search-here" } ) ;
		}
	} // End if search string long enough.
}
function addToMenuResponses() {
	// UPGRADE: Not sure if this will conflict with multi-menu implementations. Consider js_superclass...html to solve that.
	for (var itemCounter=0; itemCounter < homeMenu.itemManager.length; itemCounter++) {
		homeMenu.itemManager[itemCounter].associateResponse(   {  
			responseFun: "hideCloud( document.getElementById('thoughtmesh-header-id'), true ); document.getElementById('content').style.display='block'"
		  }    ) ;
	};
}
