/*
 * xLazyLoader 1.0 - Plugin for jQuery
 * 
 * Load js, css and  images
 *
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Depends:
 *   jquery.js
 *
 *  Copyright (c) 2008 Oleg Slobodskoi (jimdo.com)
 */

;(function($){

	$.xLazyLoader =  function ( method, options ) {
		if (typeof method == 'object') {
			options = method;
			method = 'load';
		};
		
		xLazyLoader[method]( options );
	};
	
	var xLazyLoader = new function ()
	{

		
		var head = document.getElementsByTagName("head")[0];
		
		
		this.load = function ( options )
		{
			//Defaults
			var d = {
				js: [],
				css: [],
				image: [],
				name: null,
				load: function(){}
			};
			$.extend(d, options);
			
			var self = this,
				ready = false,
				loaded = {
					js: [],
					css: [],
					image: []
				}
			;
			
			each('js', d.js);
			each('css', d.css);
			each('image', d.image);
			
			function each (type, urls)
			{
				if ( $.isArray(urls) && urls.length>0 )
					$.each( urls, function(i, url){
						load(type, url);
					});
				else if (typeof urls == 'string')
					load(type, urls);
			};

			function load (type, url)
			{
 				self[type](url, function() { 
					$.isArray(d[type]) ? loaded[type].push(url) : loaded[type] = url;
					d.js.length == loaded.js.length 
					&& d.css.length == loaded.css.length 
					&& d.image.length == loaded.image.length
					&& d.load.apply(loaded, []);
                    return;
				}, d.name ?'lazy-loaded-'+ d.name : 'lazy-loaded-'+new Date().getTime());
			};
			
		};
		
		this.js = function (src, callback, name)
		{
			if ($('script[src*="'+src+'"]').length>0) {
				callback();
				return;
			};

            var script = document.createElement('script');
            script.setAttribute("type","text/javascript");
            script.setAttribute("src", src);
            script.setAttribute('id', name);

			if ($.browser.msie)
				script.onreadystatechange = function ()	{
					 /loaded|complete/.test(script.readyState) && callback();
				}
			else
				//FF, Safari, Opera
				script.onload = callback;

			head.appendChild(script);
		};
		
		this.css = function (href, callback, name)
		{

			if ($('link[href*="'+href+'"]').length>0) {
				callback();
				return;
			};
			

			var link = $('<link rel="stylesheet" type="text/css" media="all" href="'+href+'" id="'+name+'"></link>')[0];

			if ($.browser.msie)
				link.onreadystatechange = function ()	{
                    /loaded|complete/.test(link.readyState) && callback();
				}
			else if ($.browser.opera)
				link.onload = callback;
			else
				//FF, Safari, Chrome
				(function(){
					try {
						link.sheet.cssRule;
					} catch(e){
						setTimeout(arguments.callee, 20);
						return;
					};
					callback();
				})();
			head.appendChild(link);
		};
		
		this.image = function (src, callback)
		{
			var img = new Image();
			img.onload = callback;
			img.src = src;
		};
	
		this.disable = function ( name )
		{	
			$('#lazy-loaded-'+name, head).attr('disabled', 'disabled');
		};

		this.enable = function ( name )
		{	
			$('#lazy-loaded-'+name, head).removeAttr('disabled');
		};
		
		
		this.destroy = function ( name )
		{
			$('#lazy-loaded-'+name, head).remove();	
		};
		
		
		



	};



})(jQuery);		

/**
 * Helper component to load LOMs.
 */
function LOMLoader () {
	LOMLoader.baseConstructor.call(this, null, "lomLoader");
	
	this.lastLoadedId = null;
	
	this.lomLoadedEvent = new YAHOO.util.CustomEvent("lomLoaded", this);
	this.lomLoadErrorEvent = new YAHOO.util.CustomEvent("lomLoadError", this);	
}
LOMLoader.extend(Component);

LOMLoader.loadURL = MACEConstants.componentsURL + "detailview/php/LOMLoader.php";

/**
 * Loads a LOM. After successful loading the lomLoaded event will be fired.
 * 
 * @param {String} id The id of the LOM to load.
 */
LOMLoader.prototype.loadLOM = function(id) {
	console.log("LOMLoader.loadLOM");
	
	this.lastLoadedId = id;
	
	var _this = this;
	// REVISIT get XML not possible from different server (JSONP). Wrap XML in JSON?
	// http://www.pathf.com/blogs/2007/02/101_ideas_for_j/
	$.get(LOMLoader.loadURL, {"id" : id}, function(result) {
		_this._onLomXmlLoaded(result);
	});
}	

/**
 * Private method to convert the loaded result to a LOM object and fires the lomLoaded event.
 */
LOMLoader.prototype._onLomXmlLoaded = function(result) {
	console.log("_onLomXmlLoaded");
	
	var $lomXml = $(result);
	if ($lomXml.find("lom").length > 0) {
		var lom = new LOM($lomXml);
		this.lomLoadedEvent.fire(lom);
	}
	else {
		console.warn("No LOM found for " + this.lastLoadedId);
		this.lomLoadErrorEvent.fire(result);
	}
}

/**
 * Returns lomLoaded as events this will broadcast.
 */
LOMLoader.prototype.getBroadcastEvents = function() {
	console.log("LOMLoader broadcasts: lomLoaded");
	return [this.lomLoadedEvent, this.lomLoadErrorEvent];
}

/**
 * A simple LOM object.
 *
 * Be aware this only is used for converting single loaded LOM XML into an object.
 * Multiple (e.g. search result lists) are created in PHP.
 *
 * @param {jQuery Object} xml The whole LOM XML as jQuery
 */
function LOM($lomXml){

	//console.log("new LOM", $lomXml);
	this.$xml = $lomXml;
	
	if ($lomXml.find("general").length > 1) {
		console.warn("Converting from LOM XML resulted in too many same elements.");
	}
	
	// The id of the LOM (i.e. the metaMetadata identifier)
	// Gets the only entry, or if multiple tries to find the oai one, or if not found, the first one.
	if ($lomXml.find("metaMetadata > identifier").length > 1) {
		// NB The contains-jQuery does work in IE only if patched jQuery! http://dev.jquery.com/ticket/1612
		//if ($lomXml.find("metaMetadata > identifier > catalog:contains('oai')").length > 0) {
		var $oaiCatalog = $lomXml.find("metaMetadata > identifier > catalog").filter(function(i){
			return $(this).text().startsWith("oai");
		});
		if ($oaiCatalog.length > 0) {
			this.id = $oaiCatalog.siblings("entry").text();
		}
		else {
			this.id = $lomXml.find("metaMetadata > identifier > entry:first").text()
		}
	}
	else {
		this.id = $lomXml.find("metaMetadata > identifier > entry").text();
	}
	
	// The title of the LO
	// try to find the English title
	this.title = $lomXml.find("general > title > string[language='en']").text();
	// else: pick the first one
	if (!this.title) {
		this.title = $lomXml.find("general > title > string:first").text();
	}
	
	this.shortTitle = LOM.createShortTitle(this.title);
	
	// The description of the LO
	// try to find the English description
	this.description = $lomXml.find("general > description > string[language='en']").text();
	// else: pick the first one
	if (!this.description) {
		this.description = $lomXml.find("general > description > string:first").text();
	}
	// Strips tags (as jQuery changes entity encoded angle brackets "&gt" in real ones "<" )
	this.description = this.description.replace(/<.*?>/g, "");
	
	// The original URL in repository (technical location)
	this.repositoryURL = $lomXml.find("technical > location").filter(function(index){
		return $(this).text().startsWith("http");
	}).text();
	this.url = this.repositoryURL;
	
	if (this.url == null || this.url == "") {
		this.url = this.repositoryURL = MACEConstants.detailsBaseSrc + this.id;
	}
	
	this.resourceTypes = [];
	var _this = this;
	$lomXml.find("educational > learningResourceType > value").each(function(){
		_this.resourceTypes.push($(this).text());
	});
	
	// NB: Because jQuery lacks correct namespace handling, weird construction
	this.resourceKind = null;
	var $lokValue = $lomXml.find("general mace\\:value");
	if ($lokValue.length > 0 && $lokValue.parent()[0].tagName == "mace:learningObjectKind") {
		this.resourceKind = $lokValue.text();
	}
	
	this.languageCode = $lomXml.find("general > language").text() || $lomXml.find("educational > language").text() || "en";
	this.language = LOM.languageMap[this.languageCode];
	if (Utils.isUndefined(this.language)) {
		this.language = this.languageCode;
	}
	
	// number of classification terms
	this.classificationNumber = $lomXml.find("classification > taxonPath").size();
	
	/*
	 dbpedia ID
	 */
	var _this = this;
	$lomXml.find("relation > resource > identifier").each(function(){
		if ($(this).find("catalog").text() == "dbpedia") {
			_this.dbpediaID = $(this).find("entry").text().split("dbpedia:http://dbpedia.org/resource/").join("");
			console.log("found dbpedia ID", _this.dbpediaID);
		}
	});
	
	/*
	 catalogID
	 */
	// Gets catalog from metaMetadata-ID
	this.catalog = this.id.substring(4, this.id.indexOf("."));
	
	var _this = this;
	$lomXml.find("metaMetadata > identifier > catalog").each(function(){
		var t = $(this).text();
		if (t != "oai") {
			_this.catalogID = t.toLowerCase().split("\n").join("").split("\r").join("").split(" ").join("");
		}
	});
	
	if (Utils.isUndefined(this.catalogID)) {
		this.catalogID = this.catalog;
	}
	this.catalog = LOM.guessCatalog(this.catalogID);
}

LOM.createShortTitle = function(title){
	var shortTitle = null;
	// An abbreviated title to fit better into small spaces
	if (title.length > LOM.shortTitleLength) {
		shortTitle = title.replace(new RegExp("^(.{0," + (LOM.shortTitleLength - 3) + "}\\b).*"), "$1...");
	}
	else {
		shortTitle = title;
	}
	return shortTitle;
}


LOM.shortTitleLength = 40;
LOM.languageMap = {
	"bg": "Bulgarian",
	"cs": "Czech",
	"da": "Danish",
	"nl": "Dutch",
	"en": "English",
	"et": "Estonian",
	"fi": "Finnish",
	"fr": "French",
	"de": "German",
	"el": "Greek",
	"hu": "Hungarian",
	"ga": "Irish",
	"it": "Italian",
	"lv": "Latvian",
	"lt": "Lithuanian",
	"mt": "Maltese",
	"pl": "Polish",
	"pt": "Portuguese",
	"ro": "Romanian",
	"sk": "Slovak",
	"sl": "Slovenian",
	"es": "Spanish",
	"sv": "Swedish",
	
	"hr": "Croatian",
	"li": "Limburgish",
	"ru": "Russian",
	"zh": "Chinese"
};

Catalog = function(name, URL, iconURL){
	this.name = name;
	this.URL = URL;
	this.iconURL = iconURL;
}

/**
 * Guesses a catalog based on the catalog name from the contentId.
 *
 * (See func_inc_global.php get_catalog_fromID() for same functionality)
 *
 * @param {Object} contentId
 */
LOM.guessCatalogFromId = function(contentId) {
	var key = "";
	var prefix = contentId.substring(0, contentId.indexOf(":"));
	var suffix = contentId.substring(contentId.indexOf(":") + 1, contentId.length);
	
	if (prefix == "oai") {
		// Gets first string after prefix
		// e.g. oai:dynamo.asro.kuleuven.be:1MD, oai:oaicat.iconda.org:2
		key = suffix.substring(0, suffix.indexOf("."));
	}
	else if (prefix == 'mace') {
		// Gets first string after prefix
		// e.g. mace:rwo:1MD, mace:external:2MD
		key = suffix.substring(0, suffix.indexOf(":"));
	}
	else {
		// Uses prefix
		// e.g. dbpedia:162419b3-3e1a-11de
		key = prefix;
	}
	
	var catalog = LOM.guessCatalog(key);
	if (catalog.URL == "") {
		console.log("\tcontentId=" + contentId + ", suffix=" + suffix);
	}
	return catalog;
}

/**
 * Tries to create a catalog based on a key string.
 * Key is either from LOM.catalog, or from the beginning of the ID.
 */
LOM.guessCatalog = function(key){
	var catalog = new Catalog();
	
	if (!Utils.isUndefined(key) && key != null) 
		key = key.toLowerCase();
	
	/*
    * Arch'it (it)
      digital architecture magazine
    * Archiplanet (en)
      Wiki for architecture
    * architonic (en de it fr es)
      Products, Materials and Concepts in Architecture and Design
    * ARIADNE (en)
      Literature and documents
    * CUMINCAD (en)
      articles, in full or as references, from all major CAAD conferences worldwide
    * DYNAMO (en)
      DYNAmic Memory Online - case base containing more than 1000 reviewed architectural projects
    * ICONDA (en)
      The International CONstruction DAtabase
    * WINDS (en de)
      Web-based INtelligent Design tutoring System - architectural and construction course material
	*/
	
	switch (key) {
		case "dynamo":
			catalog.name = "DYNAMO";
			catalog.URL = "http://dynamo.asro.kuleuven.be";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/dynamo.gif";
			catalog.description = "DYNAmic Memory Online: more than 1000 reviewed architectural cases";
			break;
			
		case "winds":
			catalog.name = "WINDS";
			catalog.URL = "http://raft-app.fit.fraunhofer.de/cgi-bin/WebObjects/ale-ng";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/winds.gif";
			catalog.description = "Web-based INtelligent Design tutoring System: architectural and construction course material";
			break;
			
		case "deirb":
		case "iconda":
		case "oaicat":
			catalog.name = "ICONDA";
			catalog.URL = "http://www.irbdirekt.de/iconda/ppv.htm";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/iconda.gif";
			catalog.description = "The International CONstruction DAtabase";
			break;
			
		case "mace:rwo":
		case "dbpedia":
		case "rwo":
			catalog.name = "dbpedia";
			catalog.URL = "http://dbpedia.org";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/rwo.gif";
			catalog.description = "Extracted structured information from Wikipedia by the community";
			break;
			
		case "mace:external":
		case ":external:www":
		case "external:www":
		case "external":
			catalog.name = "web";
			catalog.URL = "";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/external.gif";
			catalog.description = "External web pages";
			break;
			
		case "cumincad":			
		case "cumincad:works":
		case "cumincad : works":
			catalog.name = "CUMINCAD";
			catalog.URL = "http://cumincad.scix.net/cgi-bin/works/Home";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/cumincad.gif";
			catalog.description = "References and full articles from all major CAAD conferences worldwide";
			break;
		
		case "archiplanet":	
		case "archiplanet.org":
			catalog.name = "archiplanet";
			catalog.URL = "http://www.archiplanet.org/wiki/Main_Page";
			// TODO: create thumbnail
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/archiplanet.gif";
			catalog.description = "Wiki for architecture";
			break;
			
		case "www.nextroom.at":
			catalog.name = "nextroom";
			catalog.URL = "http://www.nextroom.at";
			// TODO: create thumbnail
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/nextroom.gif";
			catalog.description = "European architecture webzine";
			break;

		case "arch'it":			
		case "www.architettura.supereva.com":
			catalog.name = "arch'it";
			catalog.URL = "http://architettura.it";
			// TODO: create thumbnail
			catalog.description = "Digital architecture magazine";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/archit.gif";
			break;
			
		case "columbus":
		case "columbus-portal.eu":
			catalog.name = "Columbus";
			catalog.URL = "http://www.columbus-portal.eu/";
			// TODO: create thumbnail
			catalog.description = "Innovative eLearning marketplace";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/columbus.gif";
			break;
			
		case "architonic.com":
		case "www.architonic.com":
			catalog.name = "Architonic";
			catalog.URL = "http://www.architonic.com/";
			// TODO: create thumbnail
			catalog.description = "Products, Materials and Concepts in Architecture and Design";
			catalog.iconURL = MACEConstants.rootURL + "img/repositories/architonic.gif";
			break;	
			
		case "architecture.it":
			catalog.name = "Architecture.it";
			catalog.URL = "http://www.architecture.it/";
			catalog.description = "Italian architectural portal";
			break;
		
		case "mimoa.eu":
			catalog.name = "MIMOA";
			catalog.URL = "http://mimoa.eu/";
			catalog.description = "mi modern architecture - a free user generated Modern Architecture guide with addresses and maps";
			break;
		
		case "copyrightbookshop.be":
			catalog.name = "copyrightbookshop";
			catalog.URL = "http://www.copyrightbookshop.be";
			catalog.description = "Art and Architecture Bookshop";
			break;
		
		case "whc.unesco.org":
			catalog.name = "UNESCO World Heritage List of Sites";
			catalog.URL = "http://whc.unesco.org/";
			catalog.description = "UNESCO List of heritage sites";
			break;
		
		case "cad-3d.blogspot.com":
			catalog.name = "CAD-3D";
			catalog.URL = "http://cad-3d.blogspot.com/";
			catalog.description = "Blog on CAD, CAAD and 3D tools for use in architecture";
			break;
		
		case "caadasro":
			catalog.name = "CAAD@ASRO";
			catalog.URL = "http://caad.asro.kuleuven.be";
			catalog.description = "CAAD Tutorials for students of Architecture";
			break;
		
		case "macerepodb":
			catalog.name = "ASRO MACE repo DB";
			catalog.URL = "http://caad.asro.kuleuven.be/MACEREPODB/";
			catalog.description = "Database of architectural repositories";
			break;
		
		case "baufo.irbdirekt.de":
			catalog.name = "BAUFO";
			catalog.URL = "http://www.irbdirekt.de/baufo/";
			catalog.description = "Descriptions of building research projects";
			break;
		
		case "retro.seals.ch":
			catalog.name = "Baugedächtnis Schweiz Online";
			catalog.URL = "http://retro.seals.ch/";
			catalog.description = "Digitized journals 'Memory of Swiss construction'";
			break;
		
		case "archdaily.com":
			catalog.name = "Arch Daily";
			catalog.URL = "http://www.archdaily.com";
			catalog.description = "aily blog with project reviews";
			break;	
			
		default:
			console.warn("unrecognized catalog", key);
			catalog.name = key;
			catalog.URL = "";
			catalog.iconURL = "";
	}
	
	return catalog;
}



function DetailView($element) {
	DetailView.baseConstructor.call(this, $element);
	
	this.baseTitle=document.title;
	
	this.$element=$element;
	this.$element.append("<div class='message'></div>");
	this.$messageBox=this.$element.find(".message");

	this.currentId = "(No detailView.currentId)";
	
	this.HTMLtemplate = $element.html();
	$element.empty();
	
	
	// will send a jquery object of the selected LOM around, once it is loaded...
	this.LOMloadedEvent = new YAHOO.util.CustomEvent("LOMloaded", this);
}
DetailView.extend(Component);
	
DetailView.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("searchResultSelect", this.onSearchResultSelect),
		new SubscriptionEvent("lomLoaded", this.onLomLoaded)];
}

// TODO Implement as listener for LOMLoadingStartedEvent
DetailView.prototype.loadLOM = function(id){
	this.currentId=id;
	this.$element.empty();
	this.$element.append("Loading "+id);
}

DetailView.prototype.onLomLoaded = function(type, args) {
	this.lom = args[0];
	console.log("DetailView onLOMLoaded ", this.lom);	
	
	document.title = this.baseTitle + " : " + this.lom.shortTitle;
	
	//this.$element.html(this.HTMLtemplate.fillInTemplateTags(this.lom));
	this.$element.html(tmpl("headerTemplate", this.lom));
	
	//track visits of external pages
	var _this=this;
	$("#goToPageLink").click( function() {	
		app.SessionTracker.saveAction("Details", "goToPage", _this.lom.repositoryURL, _this.lom.id);
	});
	
	var copyrightString = this.lom.$xml.find("rights > description").text();
	if(!Utils.isUndefined(copyrightString) && !copyrightString.isEmpty()){
		$("#copyrightField").empty().append("<strong>Content license: </strong>"+copyrightString);
	}
	
	$("#goToPageLink").click( function() {	
		app.SessionTracker.saveAction("Details", "goToPage", _this.lom.repositoryURL, _this.lom.id);
	});
	/*
	console.log("checking wikimedia thumbnail", this.lom.dbpediaID);
	
	if(!Utils.isEmpty(this.lom.dbpediaID)){
		var thumbURL = MACEConstants.toolsURL + "detailPage/php/getWikimediaThumbnail.php?id=" + this.lom.dbpediaID;
		console.log("loading wikimedia thumbnail", thumbURL);
		$.get(thumbURL, function(data){
			if (data!=""){
				// get filename (before last slash)
				var wikimediaURL = data.split("/");
				wikimediaURL = "http://commons.wikimedia.org/wiki/File:" + wikimediaURL[wikimediaURL.length-2];
				// get larger version
				data=data.split('200px').join('300px');
				$(".pageThumb").css("background", "#444444 center center no-repeat url(" + data + ")");
				$(".pageThumb").css("height", "250px");
				$(".pageThumb").append("<div class='copyright'>Thumbnail source: <a href='" + wikimediaURL + "' target='_blank'>wikimedia</a></div>");			
			} else {
				console.log("no wikimedia image found");
			}		
			//$(".pageThumb").append("<img src='"+data)+"' />";
		});
	}
	*/
	$(".showTooltip").tipsy({fade: true, gravity:"s"});
}


function DetailPageApp() {
	DetailPageApp.baseConstructor.call(this, null, "detailPageApp");
	
	this.componentController = new ComponentController();
	
	// Registers this app itself to subscribe to events
	this.componentController.register(this);
	
	this.loginComponent = new LoginComponent($("#loginComponent"));
	this.componentController.register(this.loginComponent);
		
	this.detailView = new DetailView($("#detailView"));
	this.componentController.register(this.detailView);
	//tracker		
	this.SessionTracker = new Tracker();
	this.componentController.register(this.SessionTracker);
	
	this.lomLoader = new LOMLoader();
	this.componentController.register(this.lomLoader);
	
		
	// Widgets
	// (They need to subscribe to a "LOMloaded" event, which passes the respective LOM (as specified in LOMLoader.js)
	//////////////////////////////////////
	
	// Classification widget
	this.taxonomyComponent = new TaxonomyComponent();
	this.componentController.register(this.taxonomyComponent);
	this.taxonomyComponent.loadTaxonomy();

	this.classificationWidget = new ClassificationWidget($("#classificationWidget"));
	this.componentController.register(this.classificationWidget);
	
	// Social widgets
	this.TagWidget = new TagWidget($("#TagWidget"));
	this.componentController.register(this.TagWidget);
	
	this.RatingWidget = new RatingWidget($("#RatingWidget"));
	this.componentController.register(this.RatingWidget);
	
	this.CommentWidget = new CommentWidget($("#CommentWidget"));
	this.componentController.register(this.CommentWidget);

	// Competence Widget
	this.competenceWidget = new CompetenceWidget($("#competenceWidget"));
	this.componentController.register(this.competenceWidget);
	
	// Relation Widget
	this.relationWidget = new RelationWidget($("#relationWidget"));
	this.componentController.register(this.relationWidget);
	
	this.componentController.init();
	this.initDomElems();
	
}
DetailPageApp.extend(Component);

DetailPageApp.prototype.initDomElems = function(){

}

DetailPageApp.prototype.getSubscriptionEvents = function() {
	console.log(this.id + " .getSubscriptionEvents()");
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout),
		new SubscriptionEvent("lomLoaded", this.onLomLoaded), new SubscriptionEvent("lomLoadError", this.onLomLoadError)];
}

DetailPageApp.prototype.displayDetailsForContentId = function(id){
	$("#details").hide();
	$("#detailsLoadingMessage div").append(" for "+id).show();
	this.lomLoader.loadLOM(id);
}

DetailPageApp.prototype.onLomLoaded = function(type, args) {
	console.log("DetailPageApp.onLOMloaded. " + args[0]);
	this.$lom = args[0];
	
	$("#detailsLoadingMessage").hide();	
	$("#details").show();
	
	// Tracks Bookmarklet usage if refering from addResource page
	if(document.referrer.indexOf("addResource.php") != -1){
		this.SessionTracker.saveAction('Bookmarklet', 'add resource', args[0].title, args[0].id);
	}
	
	// Attention: Calls global (i.e. non-application) method
	loadGoogleMaps();
}

DetailPageApp.prototype.onLomLoadError = function(type, args) {
	console.log("DetailPageApp.onLomLoadError " + args[0]);
	
	$("#detailsLoadingMessage").html('<div class="errorMessage">Error loading details</div>');	
}


// REVISIT Work-Around to handle late subscribing of MapWidget


DetailPageApp.prototype.onLogin = function(type, args) {
	this.userIsLoggedIn = true;
}
DetailPageApp.prototype.onLogout = function(type, args) {
	this.userIsLoggedIn = false;
}


// From http://jquery.offput.ca/every/
// Licenced with the WTFPL

jQuery.fn.extend({
	everyTime: function(interval, label, fn, times, belay) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, times, belay);
		});
	},
	oneTime: function(interval, label, fn) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, 1);
		});
	},
	stopTime: function(label, fn) {
		return this.each(function() {
			jQuery.timer.remove(this, label, fn);
		});
	}
});

jQuery.extend({
	timer: {
		guid: 1,
		global: {},
		regex: /^([0-9]+)\s*(.*s)?$/,
		powers: {
			// Yeah this is major overkill...
			'ms': 1,
			'cs': 10,
			'ds': 100,
			's': 1000,
			'das': 10000,
			'hs': 100000,
			'ks': 1000000
		},
		timeParse: function(value) {
			if (value == undefined || value == null)
				return null;
			var result = this.regex.exec(jQuery.trim(value.toString()));
			if (result[2]) {
				var num = parseInt(result[1], 10);
				var mult = this.powers[result[2]] || 1;
				return num * mult;
			} else {
				return value;
			}
		},
		add: function(element, interval, label, fn, times, belay) {
			var counter = 0;
			
			if (jQuery.isFunction(label)) {
				if (!times) 
					times = fn;
				fn = label;
				label = interval;
			}
			
			interval = jQuery.timer.timeParse(interval);

			if (typeof interval != 'number' || isNaN(interval) || interval <= 0)
				return;

			if (times && times.constructor != Number) {
				belay = !!times;
				times = 0;
			}
			
			times = times || 0;
			belay = belay || false;
			
			if (!element.$timers) 
				element.$timers = {};
			
			if (!element.$timers[label])
				element.$timers[label] = {};
			
			fn.$timerID = fn.$timerID || this.guid++;
			
			var handler = function() {
				if (belay && this.inProgress) 
					return;
				this.inProgress = true;
				if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
					jQuery.timer.remove(element, label, fn);
				this.inProgress = false;
			};
			
			handler.$timerID = fn.$timerID;
			
			if (!element.$timers[label][fn.$timerID]) 
				element.$timers[label][fn.$timerID] = window.setInterval(handler,interval);
			
			if ( !this.global[label] )
				this.global[label] = [];
			this.global[label].push( element );
			
		},
		remove: function(element, label, fn) {
			var timers = element.$timers, ret;
			
			if ( timers ) {
				
				if (!label) {
					for ( label in timers )
						this.remove(element, label, fn);
				} else if ( timers[label] ) {
					if ( fn ) {
						if ( fn.$timerID ) {
							window.clearInterval(timers[label][fn.$timerID]);
							delete timers[label][fn.$timerID];
						}
					} else {
						for ( var fn in timers[label] ) {
							window.clearInterval(timers[label][fn]);
							delete timers[label][fn];
						}
					}
					
					for ( ret in timers[label] ) break;
					if ( !ret ) {
						ret = null;
						delete timers[label];
					}
				}
				
				for ( ret in timers ) break;
				if ( !ret ) 
					element.$timers = null;
			}
		}
	}
});

if (jQuery.browser.msie)
	jQuery(window).one("unload", function() {
		var global = jQuery.timer.global;
		for ( var label in global ) {
			var els = global[label], i = els.length;
			while ( --i )
				jQuery.timer.remove(els[i], label);
		}
	});



/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
 * $Rev: 2447 $
 *
 * Version 2.1.1
 */
(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
/*
 * Thickbox 3 - One Box To Rule Them All.
 * By Cody Lindley (http://www.codylindley.com)
 * Copyright (c) 2007 cody lindley
 * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
*/

var tb_pathToImage = "images/loadingAnimation.gif";

eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('$(o).2S(9(){1u(\'a.18, 3n.18, 3i.18\');1w=1p 1t();1w.L=2H});9 1u(b){$(b).s(9(){6 t=X.Q||X.1v||M;6 a=X.u||X.23;6 g=X.1N||P;19(t,a,g);X.2E();H P})}9 19(d,f,g){3m{3(2t o.v.J.2i==="2g"){$("v","11").r({A:"28%",z:"28%"});$("11").r("22","2Z");3(o.1Y("1F")===M){$("v").q("<U 5=\'1F\'></U><4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}n{3(o.1Y("B")===M){$("v").q("<4 5=\'B\'></4><4 5=\'8\'></4>");$("#B").s(G)}}3(1K()){$("#B").1J("2B")}n{$("#B").1J("2z")}3(d===M){d=""}$("v").q("<4 5=\'K\'><1I L=\'"+1w.L+"\' /></4>");$(\'#K\').2y();6 h;3(f.O("?")!==-1){h=f.3l(0,f.O("?"))}n{h=f}6 i=/\\.2s$|\\.2q$|\\.2m$|\\.2l$|\\.2k$/;6 j=h.1C().2h(i);3(j==\'.2s\'||j==\'.2q\'||j==\'.2m\'||j==\'.2l\'||j==\'.2k\'){1D="";1G="";14="";1z="";1x="";R="";1n="";1r=P;3(g){E=$("a[@1N="+g+"]").36();25(D=0;((D<E.1c)&&(R===""));D++){6 k=E[D].u.1C().2h(i);3(!(E[D].u==f)){3(1r){1z=E[D].Q;1x=E[D].u;R="<1e 5=\'1X\'>&1d;&1d;<a u=\'#\'>2T &2R;</a></1e>"}n{1D=E[D].Q;1G=E[D].u;14="<1e 5=\'1U\'>&1d;&1d;<a u=\'#\'>&2O; 2N</a></1e>"}}n{1r=1b;1n="1t "+(D+1)+" 2L "+(E.1c)}}}S=1p 1t();S.1g=9(){S.1g=M;6 a=2x();6 x=a[0]-1M;6 y=a[1]-1M;6 b=S.z;6 c=S.A;3(b>x){c=c*(x/b);b=x;3(c>y){b=b*(y/c);c=y}}n 3(c>y){b=b*(y/c);c=y;3(b>x){c=c*(x/b);b=x}}13=b+30;1a=c+2G;$("#8").q("<a u=\'\' 5=\'1L\' Q=\'1o\'><1I 5=\'2F\' L=\'"+f+"\' z=\'"+b+"\' A=\'"+c+"\' 23=\'"+d+"\'/></a>"+"<4 5=\'2D\'>"+d+"<4 5=\'2C\'>"+1n+14+R+"</4></4><4 5=\'2A\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4>");$("#Z").s(G);3(!(14==="")){9 12(){3($(o).N("s",12)){$(o).N("s",12)}$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1D,1G,g);H P}$("#1U").s(12)}3(!(R==="")){9 1i(){$("#8").C();$("v").q("<4 5=\'8\'></4>");19(1z,1x,g);H P}$("#1X").s(1i)}o.1h=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}n 3(I==3k){3(!(R=="")){o.1h="";1i()}}n 3(I==3j){3(!(14=="")){o.1h="";12()}}};16();$("#K").C();$("#1L").s(G);$("#8").r({Y:"T"})};S.L=f}n{6 l=f.2r(/^[^\\?]+\\??/,\'\');6 m=2p(l);13=(m[\'z\']*1)+30||3h;1a=(m[\'A\']*1)+3g||3f;W=13-30;V=1a-3e;3(f.O(\'2j\')!=-1){1E=f.1B(\'3d\');$("#15").C();3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\' Q=\'1o\'>1l</a> 1k 1j 1s</4></4><U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\' > </U>")}n{$("#B").N();$("#8").q("<U 1W=\'0\' 2d=\'0\' L=\'"+1E[0]+"\' 5=\'15\' 1v=\'15"+1f.2c(1f.1y()*2b)+"\' 1g=\'1m()\' J=\'z:"+(W+29)+"p;A:"+(V+17)+"p;\'> </U>")}}n{3($("#8").r("Y")!="T"){3(m[\'1A\']!="1b"){$("#8").q("<4 5=\'2f\'><4 5=\'1H\'>"+d+"</4><4 5=\'2e\'><a u=\'#\' 5=\'Z\'>1l</a> 1k 1j 1s</4></4><4 5=\'F\' J=\'z:"+W+"p;A:"+V+"p\'></4>")}n{$("#B").N();$("#8").q("<4 5=\'F\' 3c=\'3b\' J=\'z:"+W+"p;A:"+V+"p;\'></4>")}}n{$("#F")[0].J.z=W+"p";$("#F")[0].J.A=V+"p";$("#F")[0].3a=0;$("#1H").11(d)}}$("#Z").s(G);3(f.O(\'37\')!=-1){$("#F").q($(\'#\'+m[\'26\']).1T());$("#8").24(9(){$(\'#\'+m[\'26\']).q($("#F").1T())});16();$("#K").C();$("#8").r({Y:"T"})}n 3(f.O(\'2j\')!=-1){16();3($.1q.35){$("#K").C();$("#8").r({Y:"T"})}}n{$("#F").34(f+="&1y="+(1p 33().32()),9(){16();$("#K").C();1u("#F a.18");$("#8").r({Y:"T"})})}}3(!m[\'1A\']){o.21=9(e){3(e==M){I=2w.2v}n{I=e.2u}3(I==27){G()}}}}31(e){}}9 1m(){$("#K").C();$("#8").r({Y:"T"})}9 G(){$("#2Y").N("s");$("#Z").N("s");$("#8").2X("2W",9(){$(\'#8,#B,#1F\').2V("24").N().C()});$("#K").C();3(2t o.v.J.2i=="2g"){$("v","11").r({A:"1Z",z:"1Z"});$("11").r("22","")}o.1h="";o.21="";H P}9 16(){$("#8").r({2U:\'-\'+20((13/2),10)+\'p\',z:13+\'p\'});3(!(1V.1q.2Q&&1V.1q.2P<7)){$("#8").r({38:\'-\'+20((1a/2),10)+\'p\'})}}9 2p(a){6 b={};3(!a){H b}6 c=a.1B(/[;&]/);25(6 i=0;i<c.1c;i++){6 d=c[i].1B(\'=\');3(!d||d.1c!=2){39}6 e=2a(d[0]);6 f=2a(d[1]);f=f.2r(/\\+/g,\' \');b[e]=f}H b}9 2x(){6 a=o.2M;6 w=1S.2o||1R.2o||(a&&a.1Q)||o.v.1Q;6 h=1S.1P||1R.1P||(a&&a.2n)||o.v.2n;1O=[w,h];H 1O}9 1K(){6 a=2K.2J.1C();3(a.O(\'2I\')!=-1&&a.O(\'3o\')!=-1){H 1b}}',62,211,'|||if|div|id|var||TB_window|function||||||||||||||else|document|px|append|css|click||href|body||||width|height|TB_overlay|remove|TB_Counter|TB_TempArray|TB_ajaxContent|tb_remove|return|keycode|style|TB_load|src|null|unbind|indexOf|false|title|TB_NextHTML|imgPreloader|block|iframe|ajaxContentH|ajaxContentW|this|display|TB_closeWindowButton||html|goPrev|TB_WIDTH|TB_PrevHTML|TB_iframeContent|tb_position||thickbox|tb_show|TB_HEIGHT|true|length|nbsp|span|Math|onload|onkeydown|goNext|Esc|or|close|tb_showIframe|TB_imageCount|Close|new|browser|TB_FoundURL|Key|Image|tb_init|name|imgLoader|TB_NextURL|random|TB_NextCaption|modal|split|toLowerCase|TB_PrevCaption|urlNoQuery|TB_HideSelect|TB_PrevURL|TB_ajaxWindowTitle|img|addClass|tb_detectMacXFF|TB_ImageOff|150|rel|arrayPageSize|innerHeight|clientWidth|self|window|children|TB_prev|jQuery|frameborder|TB_next|getElementById|auto|parseInt|onkeyup|overflow|alt|unload|for|inlineId||100||unescape|1000|round|hspace|TB_closeAjaxWindow|TB_title|undefined|match|maxHeight|TB_iframe|bmp|gif|png|clientHeight|innerWidth|tb_parseQuery|jpeg|replace|jpg|typeof|which|keyCode|event|tb_getPageSize|show|TB_overlayBG|TB_closeWindow|TB_overlayMacFFBGHack|TB_secondLine|TB_caption|blur|TB_Image|60|tb_pathToImage|mac|userAgent|navigator|of|documentElement|Prev|lt|version|msie|gt|ready|Next|marginLeft|trigger|fast|fadeOut|TB_imageOff|hidden||catch|getTime|Date|load|safari|get|TB_inline|marginTop|continue|scrollTop|TB_modal|class|TB_|45|440|40|630|input|188|190|substr|try|area|firefox'.split('|'),0,{}))
/*
 * Autocomplete - jQuery plugin 1.0.2
 *
 * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
 *
 */

;(function($) {
	
$.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
		}, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function(handler) {
		return this.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.trigger("flushCache");
	},
	setOptions: function(options){
		return this.trigger("setOptions", [options]);
	},
	unautocomplete: function() {
		return this.trigger("unautocomplete");
	}
});

$.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageUp();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageDown();
				} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				select.hide();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		if (!config.mouseDownOnSelect) {
			hideResults();
		}
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {
		cache.flush();
	}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		// FIX (till) unbind keydown on its own due to bug http://dev.jquery.com/ticket/4292
		$input.unbind("keydown.autocomplete");
		$input.unbind();
		$(input.form).unbind(".autocomplete");
	});
	
	
	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
			}
			v += options.multipleSeparator;
		}
		
		$input.val(v);
		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}
	
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			return;
		}
		
		var currentValue = $input.val();
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			select.hide();
		}
	};
	
	function trimWords(value) {
		if ( !value ) {
			return [""];
		}
		var words = value.split( options.multipleSeparator );
		var result = [];
		$.each(words, function(i, value) {
			if ( $.trim(value) )
				result[i] = $.trim(value);
		});
		return result;
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.search(
				function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else
							$input.val( "" );
					}
				}
			);
		}
		if (wasVisible)
			// position cursor at end of input field
			$.Autocompleter.Selection(input, input.value.length, input.value.length);
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url,
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	};

};

$.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
	highlight: function(value, term) {
		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	},
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;
	
	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		// Updated to reg exp (Till Nagel)
		if (options.matchContains) {
			sub = sub.replace("(", "\\(");
			sub = sub.replace(")", "\\)");
			var regExp = new RegExp("\\b" + sub, "gi");
			var m = s.match(regExp);
			return (m) ? true : false;
		}
		else {
			// old mechanism, only if not match contains
			var i = s.indexOf(sub);
			if (i == -1) return false;
			return i == 0;
		}
		//return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){
			flush();
		}
		if (!data[q]){ 
			length++;
		}
		data[q] = value;
	}
	
	function populate(){
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
						var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {
								csub.push(x);
							}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;
	
	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);
	
		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		
		if( options.width > 0 )
			element.css("width", options.width);
			
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
	
	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
				top: offset.top + input.offsetHeight,
				left: offset.left
			}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.Autocompleter.Selection = function(field, start, end) {
	if( field.createTextRange ){
		var selRange = field.createTextRange();
		selRange.collapse(true);
		selRange.moveStart("character", start);
		selRange.moveEnd("character", end);
		selRange.select();
	} else if( field.setSelectionRange ){
		field.setSelectionRange(start, end);
	} else {
		if( field.selectionStart ){
			field.selectionStart = start;
			field.selectionEnd = end;
		}
	}
	field.focus();
};

})(jQuery);

/* Copyright (c) 2007 Paul Bakaus (paul.bakaus@googlemail.com) and Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate: 2007-08-17 13:14:11 -0500 (Fri, 17 Aug 2007) $
 * $Rev: 2759 $
 *
 * Version: 1.1.2
 *
 * Requires: jQuery 1.1.3+
 */

(function($){

// store a copy of the core height and width methods
var height = $.fn.height,
    width  = $.fn.width;

$.fn.extend({
	/**
	 * If used on document, returns the document's height (innerHeight).
	 * If used on window, returns the viewport's (window) height.
	 * See core docs on height() to see what happens when used on an element.
	 *
	 * @example $("#testdiv").height()
	 * @result 200
	 *
	 * @example $(document).height()
	 * @result 800
	 *
	 * @example $(window).height()
	 * @result 400
	 *
	 * @name height
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	height: function() {
		if ( !this[0] ) error();
		if ( this[0] == window )
			if ( $.browser.opera || ($.browser.safari && parseInt($.browser.version) > 520) )
				return self.innerHeight - (($(document).height() > self.innerHeight) ? getScrollbarWidth() : 0);
			else if ( $.browser.safari )
				return self.innerHeight;
			else
                return $.boxModel && document.documentElement.clientHeight || document.body.clientHeight;
		
		if ( this[0] == document ) 
			return Math.max( ($.boxModel && document.documentElement.scrollHeight || document.body.scrollHeight), document.body.offsetHeight );
		
		return height.apply(this, arguments);
	},
	
	/**
	 * If used on document, returns the document's width (innerWidth).
	 * If used on window, returns the viewport's (window) width.
	 * See core docs on width() to see what happens when used on an element.
	 *
	 * @example $("#testdiv").width()
	 * @result 200
	 *
	 * @example $(document).width()
	 * @result 800
	 *
	 * @example $(window).width()
	 * @result 400
	 *
	 * @name width
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	width: function() {
		if (!this[0]) error();
		if ( this[0] == window )
			if ( $.browser.opera || ($.browser.safari && parseInt($.browser.version) > 520) )
				return self.innerWidth - (($(document).width() > self.innerWidth) ? getScrollbarWidth() : 0);
			else if ( $.browser.safari )
				return self.innerWidth;
			else
                return $.boxModel && document.documentElement.clientWidth || document.body.clientWidth;

		if ( this[0] == document )
			if ($.browser.mozilla) {
				// mozilla reports scrollWidth and offsetWidth as the same
				var scrollLeft = self.pageXOffset;
				self.scrollTo(99999999, self.pageYOffset);
				var scrollWidth = self.pageXOffset;
				self.scrollTo(scrollLeft, self.pageYOffset);
				return document.body.offsetWidth + scrollWidth;
			}
			else 
				return Math.max( (($.boxModel && !$.browser.safari) && document.documentElement.scrollWidth || document.body.scrollWidth), document.body.offsetWidth );

		return width.apply(this, arguments);
	},
	
	/**
	 * Gets the inner height (excludes the border and includes the padding) for the first matched element.
	 * If used on document, returns the document's height (innerHeight).
	 * If used on window, returns the viewport's (window) height.
	 *
	 * @example $("#testdiv").innerHeight()
	 * @result 210
	 *
	 * @name innerHeight
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	innerHeight: function() {
		if (!this[0]) error();
		return this[0] == window || this[0] == document ?
			this.height() :
			this.is(':visible') ?
				this[0].offsetHeight - num(this, 'borderTopWidth') - num(this, 'borderBottomWidth') :
				this.height() + num(this, 'paddingTop') + num(this, 'paddingBottom');
	},
	
	/**
	 * Gets the inner width (excludes the border and includes the padding) for the first matched element.
	 * If used on document, returns the document's width (innerWidth).
	 * If used on window, returns the viewport's (window) width.
	 *
	 * @example $("#testdiv").innerWidth()
	 * @result 210
	 *
	 * @name innerWidth
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	innerWidth: function() {
		if (!this[0]) error();
		return this[0] == window || this[0] == document ?
			this.width() :
			this.is(':visible') ?
				this[0].offsetWidth - num(this, 'borderLeftWidth') - num(this, 'borderRightWidth') :
				this.width() + num(this, 'paddingLeft') + num(this, 'paddingRight');
	},
	
	/**
	 * Gets the outer height (includes the border and padding) for the first matched element.
	 * If used on document, returns the document's height (innerHeight).
	 * If used on window, returns the viewport's (window) height.
	 *
	 * The margin can be included in the calculation by passing an options map with margin
	 * set to true.
	 *
	 * @example $("#testdiv").outerHeight()
	 * @result 220
	 *
	 * @example $("#testdiv").outerHeight({ margin: true })
	 * @result 240
	 *
	 * @name outerHeight
	 * @type Number
	 * @param Map options Optional settings to configure the way the outer height is calculated.
	 * @cat Plugins/Dimensions
	 */
	outerHeight: function(options) {
		if (!this[0]) error();
		options = $.extend({ margin: false }, options || {});
		return this[0] == window || this[0] == document ?
			this.height() :
			this.is(':visible') ?
				this[0].offsetHeight + (options.margin ? (num(this, 'marginTop') + num(this, 'marginBottom')) : 0) :
				this.height() 
					+ num(this,'borderTopWidth') + num(this, 'borderBottomWidth') 
					+ num(this, 'paddingTop') + num(this, 'paddingBottom')
					+ (options.margin ? (num(this, 'marginTop') + num(this, 'marginBottom')) : 0);
	},
	
	/**
	 * Gets the outer width (including the border and padding) for the first matched element.
	 * If used on document, returns the document's width (innerWidth).
	 * If used on window, returns the viewport's (window) width.
	 *
	 * The margin can be included in the calculation by passing an options map with margin
	 * set to true.
	 *
	 * @example $("#testdiv").outerWidth()
	 * @result 1000
	 *
	 * @example $("#testdiv").outerWidth({ margin: true })
	 * @result 1020
	 * 
	 * @name outerHeight
	 * @type Number
	 * @param Map options Optional settings to configure the way the outer width is calculated.
	 * @cat Plugins/Dimensions
	 */
	outerWidth: function(options) {
		if (!this[0]) error();
		options = $.extend({ margin: false }, options || {});
		return this[0] == window || this[0] == document ?
			this.width() :
			this.is(':visible') ?
				this[0].offsetWidth + (options.margin ? (num(this, 'marginLeft') + num(this, 'marginRight')) : 0) :
				this.width() 
					+ num(this, 'borderLeftWidth') + num(this, 'borderRightWidth') 
					+ num(this, 'paddingLeft') + num(this, 'paddingRight')
					+ (options.margin ? (num(this, 'marginLeft') + num(this, 'marginRight')) : 0);
	},
	
	/**
	 * Gets how many pixels the user has scrolled to the right (scrollLeft).
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $(window).scrollLeft()
	 * @result 100
	 *
	 * @example $(document).scrollLeft()
	 * @result 100
	 * 
	 * @example $("#testdiv").scrollLeft()
	 * @result 100
	 *
	 * @name scrollLeft
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	/**
	 * Sets the scrollLeft property for each element and continues the chain.
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $(window).scrollLeft(100).scrollLeft()
	 * @result 100
	 * 
	 * @example $(document).scrollLeft(100).scrollLeft()
	 * @result 100
	 *
	 * @example $("#testdiv").scrollLeft(100).scrollLeft()
	 * @result 100
	 *
	 * @name scrollLeft
	 * @param Number value A positive number representing the desired scrollLeft.
	 * @type jQuery
	 * @cat Plugins/Dimensions
	 */
	scrollLeft: function(val) {
		if (!this[0]) error();
		if ( val != undefined )
			// set the scroll left
			return this.each(function() {
				if (this == window || this == document)
					window.scrollTo( val, $(window).scrollTop() );
				else
					this.scrollLeft = val;
			});
		
		// return the scroll left offest in pixels
		if ( this[0] == window || this[0] == document )
			return self.pageXOffset ||
				$.boxModel && document.documentElement.scrollLeft ||
				document.body.scrollLeft;
				
		return this[0].scrollLeft;
	},
	
	/**
	 * Gets how many pixels the user has scrolled to the bottom (scrollTop).
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $(window).scrollTop()
	 * @result 100
	 *
	 * @example $(document).scrollTop()
	 * @result 100
	 * 
	 * @example $("#testdiv").scrollTop()
	 * @result 100
	 *
	 * @name scrollTop
	 * @type Number
	 * @cat Plugins/Dimensions
	 */
	/**
	 * Sets the scrollTop property for each element and continues the chain.
	 * Works on containers with overflow: auto and window/document.
	 *
	 * @example $(window).scrollTop(100).scrollTop()
	 * @result 100
	 * 
	 * @example $(document).scrollTop(100).scrollTop()
	 * @result 100
	 *
	 * @example $("#testdiv").scrollTop(100).scrollTop()
	 * @result 100
	 *
	 * @name scrollTop
	 * @param Number value A positive number representing the desired scrollTop.
	 * @type jQuery
	 * @cat Plugins/Dimensions
	 */
	scrollTop: function(val) {
		if (!this[0]) error();
		if ( val != undefined )
			// set the scroll top
			return this.each(function() {
				if (this == window || this == document)
					window.scrollTo( $(window).scrollLeft(), val );
				else
					this.scrollTop = val;
			});
		
		// return the scroll top offset in pixels
		if ( this[0] == window || this[0] == document )
			return self.pageYOffset ||
				$.boxModel && document.documentElement.scrollTop ||
				document.body.scrollTop;

		return this[0].scrollTop;
	},
	
	/** 
	 * Gets the top and left positioned offset in pixels.
	 * The positioned offset is the offset between a positioned
	 * parent and the element itself.
	 *
	 * For accurate calculations make sure to use pixel values for margins, borders and padding.
	 *
	 * @example $("#testdiv").position()
	 * @result { top: 100, left: 100 }
	 *
	 * @example var position = {};
	 * $("#testdiv").position(position)
	 * @result position = { top: 100, left: 100 }
	 * 
	 * @name position
	 * @param Object returnObject Optional An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	position: function(returnObject) {
		return this.offset({ margin: false, scroll: false, relativeTo: this.offsetParent() }, returnObject);
	},
	
	/**
	 * Gets the location of the element in pixels from the top left corner of the viewport.
	 * The offset method takes an optional map of key value pairs to configure the way
	 * the offset is calculated. Here are the different options.
	 *
	 * (Boolean) margin - Should the margin of the element be included in the calculations? True by default.
	 * (Boolean) border - Should the border of the element be included in the calculations? False by default. 
	 * (Boolean) padding - Should the padding of the element be included in the calculations? False by default. 
	 * (Boolean) scroll - Should the scroll offsets of the parent elements be included in the calculations? True by default.
	 *                    When true it adds the total scroll offsets of all parents to the total offset and also adds two
	 *                    properties to the returned object, scrollTop and scrollLeft.
	 * (Boolean) lite - When true it will use the offsetLite method instead of the full-blown, slower offset method. False by default.
	 *                  Only use this when margins, borders and padding calculations don't matter.
	 * (HTML Element) relativeTo - This should be a parent of the element and should have position (like absolute or relative).
	 *                             It will retreive the offset relative to this parent element. By default it is the body element.
	 *
	 * Also an object can be passed as the second paramater to
	 * catch the value of the return and continue the chain.
	 *
	 * For accurate calculations make sure to use pixel values for margins, borders and padding.
	 * 
	 * Known issues:
	 *  - Issue: A div positioned relative or static without any content before it and its parent will report an offsetTop of 0 in Safari
	 *    Workaround: Place content before the relative div ... and set height and width to 0 and overflow to hidden
	 *
	 * @example $("#testdiv").offset()
	 * @result { top: 100, left: 100, scrollTop: 10, scrollLeft: 10 }
	 *
	 * @example $("#testdiv").offset({ scroll: false })
	 * @result { top: 90, left: 90 }
	 *
	 * @example var offset = {}
	 * $("#testdiv").offset({ scroll: false }, offset)
	 * @result offset = { top: 90, left: 90 }
	 *
	 * @name offset
	 * @param Map options Optional settings to configure the way the offset is calculated.
	 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	offset: function(options, returnObject) {
		if (!this[0]) error();
		var x = 0, y = 0, sl = 0, st = 0,
		    elem = this[0], parent = this[0], op, parPos, elemPos = $.css(elem, 'position'),
		    mo = $.browser.mozilla, ie = $.browser.msie, oa = $.browser.opera,
		    sf = $.browser.safari, sf3 = $.browser.safari && parseInt($.browser.version) > 520,
		    absparent = false, relparent = false, 
		    options = $.extend({ margin: true, border: false, padding: false, scroll: true, lite: false, relativeTo: document.body }, options || {});
		
		// Use offsetLite if lite option is true
		if (options.lite) return this.offsetLite(options, returnObject);
		// Get the HTMLElement if relativeTo is a jquery collection
		if (options.relativeTo.jquery) options.relativeTo = options.relativeTo[0];
		
		if (elem.tagName == 'BODY') {
			// Safari 2 is the only one to get offsetLeft and offsetTop properties of the body "correct"
			// Except they all mess up when the body is positioned absolute or relative
			x = elem.offsetLeft;
			y = elem.offsetTop;
			// Mozilla ignores margin and subtracts border from body element
			if (mo) {
				x += num(elem, 'marginLeft') + (num(elem, 'borderLeftWidth')*2);
				y += num(elem, 'marginTop')  + (num(elem, 'borderTopWidth') *2);
			} else
			// Opera ignores margin
			if (oa) {
				x += num(elem, 'marginLeft');
				y += num(elem, 'marginTop');
			} else
			// IE does not add the border in Standards Mode
			if ((ie && jQuery.boxModel)) {
				x += num(elem, 'borderLeftWidth');
				y += num(elem, 'borderTopWidth');
			} else
			// Safari 3 doesn't not include border or margin
			if (sf3) {
				x += num(elem, 'marginLeft') + num(elem, 'borderLeftWidth');
				y += num(elem, 'marginTop')  + num(elem, 'borderTopWidth');
			}
		} else {
			do {
				parPos = $.css(parent, 'position');
			
				x += parent.offsetLeft;
				y += parent.offsetTop;

				// Mozilla and IE do not add the border
				// Mozilla adds the border for table cells
				if ((mo && !parent.tagName.match(/^t[d|h]$/i)) || ie || sf3) {
					// add borders to offset
					x += num(parent, 'borderLeftWidth');
					y += num(parent, 'borderTopWidth');

					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					if (mo && parPos == 'absolute') absparent = true;
					// IE does not include the border on the body if an element is position static and without an absolute or relative parent
					if (ie && parPos == 'relative') relparent = true;
				}

				op = parent.offsetParent || document.body;
				if (options.scroll || mo) {
					do {
						if (options.scroll) {
							// get scroll offsets
							sl += parent.scrollLeft;
							st += parent.scrollTop;
						}
						
						// Opera sometimes incorrectly reports scroll offset for elements with display set to table-row or inline
						if (oa && ($.css(parent, 'display') || '').match(/table-row|inline/)) {
							sl = sl - ((parent.scrollLeft == parent.offsetLeft) ? parent.scrollLeft : 0);
							st = st - ((parent.scrollTop == parent.offsetTop) ? parent.scrollTop : 0);
						}
				
						// Mozilla does not add the border for a parent that has overflow set to anything but visible
						if (mo && parent != elem && $.css(parent, 'overflow') != 'visible') {
							x += num(parent, 'borderLeftWidth');
							y += num(parent, 'borderTopWidth');
						}
				
						parent = parent.parentNode;
					} while (parent != op);
				}
				parent = op;
				
				// exit the loop if we are at the relativeTo option but not if it is the body or html tag
				if (parent == options.relativeTo && !(parent.tagName == 'BODY' || parent.tagName == 'HTML'))  {
					// Mozilla does not add the border for a parent that has overflow set to anything but visible
					if (mo && parent != elem && $.css(parent, 'overflow') != 'visible') {
						x += num(parent, 'borderLeftWidth');
						y += num(parent, 'borderTopWidth');
					}
					// Safari 2 and opera includes border on positioned parents
					if ( ((sf && !sf3) || oa) && parPos != 'static' ) {
						x -= num(op, 'borderLeftWidth');
						y -= num(op, 'borderTopWidth');
					}
					break;
				}
				if (parent.tagName == 'BODY' || parent.tagName == 'HTML') {
					// Safari 2 and IE Standards Mode doesn't add the body margin for elments positioned with static or relative
					if (((sf && !sf3) || (ie && $.boxModel)) && elemPos != 'absolute' && elemPos != 'fixed') {
						x += num(parent, 'marginLeft');
						y += num(parent, 'marginTop');
					}
					// Safari 3 does not include the border on body
					// Mozilla does not include the border on body if an element isn't positioned absolute and is without an absolute parent
					// IE does not include the border on the body if an element is positioned static and without an absolute or relative parent
					if ( sf3 || (mo && !absparent && elemPos != 'fixed') || 
					     (ie && elemPos == 'static' && !relparent) ) {
						x += num(parent, 'borderLeftWidth');
						y += num(parent, 'borderTopWidth');
					}
					break; // Exit the loop
				}
			} while (parent);
		}

		var returnValue = handleOffsetReturn(elem, options, x, y, sl, st);

		if (returnObject) { $.extend(returnObject, returnValue); return this; }
		else              { return returnValue; }
	},
	
	/**
	 * Gets the location of the element in pixels from the top left corner of the viewport.
	 * This method is much faster than offset but not as accurate when borders and margins are
	 * on the element and/or its parents. This method can be invoked
	 * by setting the lite option to true in the offset method.
	 * The offsetLite method takes an optional map of key value pairs to configure the way
	 * the offset is calculated. Here are the different options.
	 *
	 * (Boolean) margin - Should the margin of the element be included in the calculations? True by default.
	 * (Boolean) border - Should the border of the element be included in the calculations? False by default. 
	 * (Boolean) padding - Should the padding of the element be included in the calcuations? False by default. 
	 * (Boolean) scroll - Sould the scroll offsets of the parent elements be included int he calculations? True by default.
	 *                    When true it adds the total scroll offsets of all parents to the total offset and also adds two
	 *                    properties to the returned object, scrollTop and scrollLeft.
	 * (HTML Element) relativeTo - This should be a parent of the element and should have position (like absolute or relative).
	 *                             It will retreive the offset relative to this parent element. By default it is the body element.
	 *
	 * @name offsetLite
	 * @param Map options Optional settings to configure the way the offset is calculated.
	 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
	 *                            chain will not be broken and the result will be assigned to this object.
	 * @type Object
	 * @cat Plugins/Dimensions
	 */
	offsetLite: function(options, returnObject) {
		if (!this[0]) error();
		var x = 0, y = 0, sl = 0, st = 0, parent = this[0], offsetParent, 
		    options = $.extend({ margin: true, border: false, padding: false, scroll: true, relativeTo: document.body }, options || {});
				
		// Get the HTMLElement if relativeTo is a jquery collection
		if (options.relativeTo.jquery) options.relativeTo = options.relativeTo[0];
		
		do {
			x += parent.offsetLeft;
			y += parent.offsetTop;

			offsetParent = parent.offsetParent || document.body;
			if (options.scroll) {
				// get scroll offsets
				do {
					sl += parent.scrollLeft;
					st += parent.scrollTop;
					parent = parent.parentNode;
				} while(parent != offsetParent);
			}
			parent = offsetParent;
		} while (parent && parent.tagName != 'BODY' && parent.tagName != 'HTML' && parent != options.relativeTo);

		var returnValue = handleOffsetReturn(this[0], options, x, y, sl, st);

		if (returnObject) { $.extend(returnObject, returnValue); return this; }
		else              { return returnValue; }
	},
	
	/**
	 * Returns a jQuery collection with the positioned parent of 
	 * the first matched element. This is the first parent of 
	 * the element that has position (as in relative or absolute).
	 *
	 * @name offsetParent
	 * @type jQuery
	 * @cat Plugins/Dimensions
	 */
	offsetParent: function() {
		if (!this[0]) error();
		var offsetParent = this[0].offsetParent;
		while ( offsetParent && (offsetParent.tagName != 'BODY' && $.css(offsetParent, 'position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return $(offsetParent);
	}
});

/**
 * Throws an error message when no elements are in the jQuery collection
 * @private
 */
var error = function() {
	throw "Dimensions: jQuery collection is empty";
};

/**
 * Handles converting a CSS Style into an Integer.
 * @private
 */
var num = function(el, prop) {
	return parseInt($.css(el.jquery?el[0]:el,prop))||0;
};

/**
 * Handles the return value of the offset and offsetLite methods.
 * @private
 */
var handleOffsetReturn = function(elem, options, x, y, sl, st) {
	if ( !options.margin ) {
		x -= num(elem, 'marginLeft');
		y -= num(elem, 'marginTop');
	}

	// Safari and Opera do not add the border for the element
	if ( options.border && (($.browser.safari && parseInt($.browser.version) < 520) || $.browser.opera) ) {
		x += num(elem, 'borderLeftWidth');
		y += num(elem, 'borderTopWidth');
	} else if ( !options.border && !(($.browser.safari && parseInt($.browser.version) < 520) || $.browser.opera) ) {
		x -= num(elem, 'borderLeftWidth');
		y -= num(elem, 'borderTopWidth');
	}

	if ( options.padding ) {
		x += num(elem, 'paddingLeft');
		y += num(elem, 'paddingTop');
	}
	
	// do not include scroll offset on the element ... opera sometimes reports scroll offset as actual offset
	if ( options.scroll && (!$.browser.opera || elem.offsetLeft != elem.scrollLeft && elem.offsetTop != elem.scrollLeft) ) {
		sl -= elem.scrollLeft;
		st -= elem.scrollTop;
	}

	return options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
	                      : { top: y, left: x };
};

/**
 * Gets the width of the OS scrollbar
 * @private
 */
var scrollbarWidth = 0;
var getScrollbarWidth = function() {
	if (!scrollbarWidth) {
		var testEl = $('<div>')
				.css({
					width: 100,
					height: 100,
					overflow: 'auto',
					position: 'absolute',
					top: -1000,
					left: -1000
				})
				.appendTo('body');
		scrollbarWidth = 100 - testEl
			.append('<div>')
			.find('div')
				.css({
					width: '100%',
					height: 200
				})
				.width();
		testEl.remove();
	}
	return scrollbarWidth;
};

})(jQuery);
/**
 * jCarousel - Riding carousels with jQuery
 *   http://sorgalla.com/jcarousel/
 *
 * Copyright (c) 2006 Jan Sorgalla (http://sorgalla.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * Built on top of the jQuery library
 *   http://jquery.com
 *
 * Inspired by the "Carousel Component" by Bill Scott
 *   http://billwscott.com/carousel/
 */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(9($){$.1s.B=9(o){z 4.1b(9(){3h r(4,o)})};8 q={W:J,23:1,1X:1,u:7,15:3,17:7,1I:\'2O\',2b:\'2E\',1i:0,C:7,1h:7,1D:7,2x:7,2w:7,2v:7,2t:7,2r:7,2q:7,2o:7,1Q:\'<Y></Y>\',1P:\'<Y></Y>\',2k:\'2j\',2g:\'2j\',1L:7,1J:7};$.B=9(e,o){4.5=$.1a({},q,o||{});4.P=J;4.E=7;4.H=7;4.t=7;4.U=7;4.Q=7;4.N=!4.5.W?\'1E\':\'27\';4.F=!4.5.W?\'26\':\'25\';6(e.20==\'3p\'||e.20==\'3n\'){4.t=$(e);4.E=4.t.1p();6($.D.1e(4.E[0].D,\'B-H\')){6(!$.D.1e(4.E[0].3k.D,\'B-E\'))4.E=4.E.C(\'<Y></Y>\');4.E=4.E.1p()}10 6(!$.D.1e(4.E[0].D,\'B-E\'))4.E=4.t.C(\'<Y></Y>\').1p();8 a=e.D.3g(\' \');1n(8 i=0;i<a.O;i++){6(a[i].3c(\'B-3b\')!=-1){4.t.1y(a[i]);4.E.R(a[i]);1m}}}10{4.E=$(e);4.t=$(e).2m(\'32,2Z\')}4.H=4.t.1p();6(!4.H.O||!$.D.1e(4.H[0].D,\'B-H\'))4.H=4.t.C(\'<Y></Y>\').1p();4.Q=$(\'.B-13\',4.E);6(4.Q.u()==0&&4.5.1P!=7)4.Q=4.H.1w(4.5.1P).13();4.Q.R(4.D(\'B-13\'));4.U=$(\'.B-16\',4.E);6(4.U.u()==0&&4.5.1Q!=7)4.U=4.H.1w(4.5.1Q).13();4.U.R(4.D(\'B-16\'));4.H.R(4.D(\'B-H\'));4.t.R(4.D(\'B-t\'));4.E.R(4.D(\'B-E\'));8 b=4.5.17!=7?1k.1M(4.1j()/4.5.17):7;8 c=4.t.2m(\'1u\');8 d=4;6(c.u()>0){8 f=0,i=4.5.1X;c.1b(9(){d.1O(4,i++);f+=d.T(4,b)});4.t.y(4.N,f+\'S\');6(!o||o.u==L)4.5.u=c.u()}4.E.y(\'1x\',\'1B\');4.U.y(\'1x\',\'1B\');4.Q.y(\'1x\',\'1B\');4.2p=9(){d.16()};4.2s=9(){d.13()};$(2D).1W(\'2B\',9(){d.29()});6(4.5.1h!=7)4.5.1h(4,\'28\');4.1F()};8 r=$.B;r.1s=r.2z={B:\'0.2.2\'};r.1s.1a=r.1a=$.1a;r.1s.1a({1F:9(){4.A=7;4.G=7;4.Z=7;4.11=7;4.14=J;4.1c=7;4.M=7;4.V=J;6(4.P)z;4.t.y(4.F,4.1r(4.5.1X)+\'S\');8 p=4.1r(4.5.23);4.Z=4.11=7;4.1g(p,J)},24:9(){4.t.22();4.t.y(4.F,\'21\');4.t.y(4.N,\'21\');6(4.5.1h!=7)4.5.1h(4,\'24\');4.1F()},29:9(){6(4.M!=7&&4.V)4.t.y(4.F,r.K(4.t.y(4.F))+4.M);4.M=7;4.V=J;6(4.5.1D!=7)4.5.1D(4);6(4.5.17!=7){8 a=4;8 b=1k.1M(4.1j()/4.5.17),N=0,F=0;$(\'1u\',4.t).1b(9(i){N+=a.T(4,b);6(i+1<a.A)F=N});4.t.y(4.N,N+\'S\');4.t.y(4.F,-F+\'S\')}4.15(4.A,J)},2y:9(){4.P=1f;4.1q()},3m:9(){4.P=J;4.1q()},u:9(s){6(s!=L){4.5.u=s;6(!4.P)4.1q()}z 4.5.u},1e:9(i,a){6(a==L||!a)a=i;1n(8 j=i;j<=a;j++){8 e=4.I(j).I(0);6(!e||$.D.1e(e,\'B-19-1C\'))z J}z 1f},I:9(i){z $(\'.B-19-\'+i,4.t)},3l:9(i,s){8 e=4.I(i),1Y=0;6(e.O==0){8 c,e=4.1A(i),j=r.K(i);1o(c=4.I(--j)){6(j<=0||c.O){j<=0?4.t.2u(e):c.1V(e);1m}}}10 1Y=4.T(e);e.1y(4.D(\'B-19-1C\'));1U s==\'3j\'?e.3f(s):e.22().3d(s);8 a=4.5.17!=7?1k.1M(4.1j()/4.5.17):7;8 b=4.T(e,a)-1Y;6(i>0&&i<4.A)4.t.y(4.F,r.K(4.t.y(4.F))+b+\'S\');4.t.y(4.N,r.K(4.t.y(4.N))+b+\'S\');z e},1T:9(i){8 e=4.I(i);6(!e.O||(i>=4.A&&i<=4.G))z;8 d=4.T(e);6(i<4.A)4.t.y(4.F,r.K(4.t.y(4.F))+d+\'S\');e.1T();4.t.y(4.N,r.K(4.t.y(4.N))-d+\'S\')},16:9(){4.1z();6(4.M!=7&&!4.V)4.1S(J);10 4.15(((4.5.C==\'1R\'||4.5.C==\'G\')&&4.5.u!=7&&4.G==4.5.u)?1:4.A+4.5.15)},13:9(){4.1z();6(4.M!=7&&4.V)4.1S(1f);10 4.15(((4.5.C==\'1R\'||4.5.C==\'A\')&&4.5.u!=7&&4.A==1)?4.5.u:4.A-4.5.15)},1S:9(b){6(4.P||4.14||!4.M)z;8 a=r.K(4.t.y(4.F));!b?a-=4.M:a+=4.M;4.V=!b;4.Z=4.A;4.11=4.G;4.1g(a)},15:9(i,a){6(4.P||4.14)z;4.1g(4.1r(i),a)},1r:9(i){6(4.P||4.14)z;6(4.5.C!=\'18\')i=i<1?1:(4.5.u&&i>4.5.u?4.5.u:i);8 a=4.A>i;8 b=r.K(4.t.y(4.F));8 f=4.5.C!=\'18\'&&4.A<=1?1:4.A;8 c=a?4.I(f):4.I(4.G);8 j=a?f:f-1;8 e=7,l=0,p=J,d=0;1o(a?--j>=i:++j<i){e=4.I(j);p=!e.O;6(e.O==0){e=4.1A(j).R(4.D(\'B-19-1C\'));c[a?\'1w\':\'1V\'](e)}c=e;d=4.T(e);6(p)l+=d;6(4.A!=7&&(4.5.C==\'18\'||(j>=1&&(4.5.u==7||j<=4.5.u))))b=a?b+d:b-d}8 g=4.1j();8 h=[];8 k=0,j=i,v=0;8 c=4.I(i-1);1o(++k){e=4.I(j);p=!e.O;6(e.O==0){e=4.1A(j).R(4.D(\'B-19-1C\'));c.O==0?4.t.2u(e):c[a?\'1w\':\'1V\'](e)}c=e;8 d=4.T(e);6(d==0){3a(\'39: 38 1E/27 37 1n 36. 35 34 33 31 30 2Y. 2X...\');z 0}6(4.5.C!=\'18\'&&4.5.u!==7&&j>4.5.u)h.2W(e);10 6(p)l+=d;v+=d;6(v>=g)1m;j++}1n(8 x=0;x<h.O;x++)h[x].1T();6(l>0){4.t.y(4.N,4.T(4.t)+l+\'S\');6(a){b-=l;4.t.y(4.F,r.K(4.t.y(4.F))-l+\'S\')}}8 n=i+k-1;6(4.5.C!=\'18\'&&4.5.u&&n>4.5.u)n=4.5.u;6(j>n){k=0,j=n,v=0;1o(++k){8 e=4.I(j--);6(!e.O)1m;v+=4.T(e);6(v>=g)1m}}8 o=n-k+1;6(4.5.C!=\'18\'&&o<1)o=1;6(4.V&&a){b+=4.M;4.V=J}4.M=7;6(4.5.C!=\'18\'&&n==4.5.u&&(n-k+1)>=1){8 m=r.X(4.I(n),!4.5.W?\'1l\':\'1H\');6((v-m)>g)4.M=v-g-m}1o(i-->o)b+=4.T(4.I(i));4.Z=4.A;4.11=4.G;4.A=o;4.G=n;z b},1g:9(p,a){6(4.P||4.14)z;4.14=1f;8 b=4;8 c=9(){b.14=J;6(p==0)b.t.y(b.F,0);6(b.5.C==\'1R\'||b.5.C==\'G\'||b.5.u==7||b.G<b.5.u)b.2i();b.1q();b.1N(\'2h\')};4.1N(\'2V\');6(!4.5.1I||a==J){4.t.y(4.F,p+\'S\');c()}10{8 o=!4.5.W?{\'26\':p}:{\'25\':p};4.t.1g(o,4.5.1I,4.5.2b,c)}},2i:9(s){6(s!=L)4.5.1i=s;6(4.5.1i==0)z 4.1z();6(4.1c!=7)z;8 a=4;4.1c=2U(9(){a.16()},4.5.1i*2T)},1z:9(){6(4.1c==7)z;2S(4.1c);4.1c=7},1q:9(n,p){6(n==L||n==7){8 n=!4.P&&4.5.u!==0&&((4.5.C&&4.5.C!=\'A\')||4.5.u==7||4.G<4.5.u);6(!4.P&&(!4.5.C||4.5.C==\'A\')&&4.5.u!=7&&4.G>=4.5.u)n=4.M!=7&&!4.V}6(p==L||p==7){8 p=!4.P&&4.5.u!==0&&((4.5.C&&4.5.C!=\'G\')||4.A>1);6(!4.P&&(!4.5.C||4.5.C==\'G\')&&4.5.u!=7&&4.A==1)p=4.M!=7&&4.V}8 a=4;4.U[n?\'1W\':\'2f\'](4.5.2k,4.2p)[n?\'1y\':\'R\'](4.D(\'B-16-1v\')).1K(\'1v\',n?J:1f);4.Q[p?\'1W\':\'2f\'](4.5.2g,4.2s)[p?\'1y\':\'R\'](4.D(\'B-13-1v\')).1K(\'1v\',p?J:1f);6(4.U.O>0&&(4.U[0].1d==L||4.U[0].1d!=n)&&4.5.1L!=7){4.U.1b(9(){a.5.1L(a,4,n)});4.U[0].1d=n}6(4.Q.O>0&&(4.Q[0].1d==L||4.Q[0].1d!=p)&&4.5.1J!=7){4.Q.1b(9(){a.5.1J(a,4,p)});4.Q[0].1d=p}},1N:9(a){8 b=4.Z==7?\'28\':(4.Z<4.A?\'16\':\'13\');4.12(\'2x\',a,b);6(4.Z!=4.A){4.12(\'2w\',a,b,4.A);4.12(\'2v\',a,b,4.Z)}6(4.11!=4.G){4.12(\'2t\',a,b,4.G);4.12(\'2r\',a,b,4.11)}4.12(\'2q\',a,b,4.A,4.G,4.Z,4.11);4.12(\'2o\',a,b,4.Z,4.11,4.A,4.G)},12:9(a,b,c,d,e,f,g){6(4.5[a]==L||(1U 4.5[a]!=\'2e\'&&b!=\'2h\'))z;8 h=1U 4.5[a]==\'2e\'?4.5[a][b]:4.5[a];6(!$.2R(h))z;8 j=4;6(d===L)h(j,c,b);10 6(e===L)4.I(d).1b(9(){h(j,4,d,c,b)});10{1n(8 i=d;i<=e;i++)6(!(i>=f&&i<=g))4.I(i).1b(9(){h(j,4,i,c,b)})}},1A:9(i){z 4.1O(\'<1u></1u>\',i)},1O:9(e,i){8 a=$(e).R(4.D(\'B-19\')).R(4.D(\'B-19-\'+i));a.1K(\'2Q\',i);z a},D:9(c){z c+\' \'+c+(!4.5.W?\'-2P\':\'-W\')},T:9(e,d){8 a=e.2l!=L?e[0]:e;8 b=!4.5.W?a.1t+r.X(a,\'2d\')+r.X(a,\'1l\'):a.2c+r.X(a,\'2n\')+r.X(a,\'1H\');6(d==L||b==d)z b;8 w=!4.5.W?d-r.X(a,\'2d\')-r.X(a,\'1l\'):d-r.X(a,\'2n\')-r.X(a,\'1H\');$(a).y(4.N,w+\'S\');z 4.T(a)},1j:9(){z!4.5.W?4.H[0].1t-r.K(4.H.y(\'2N\'))-r.K(4.H.y(\'2M\')):4.H[0].2c-r.K(4.H.y(\'2L\'))-r.K(4.H.y(\'2K\'))},2J:9(i,s){6(s==L)s=4.5.u;z 1k.2I((((i-1)/s)-1k.3e((i-1)/s))*s)+1}});r.1a({2H:9(d){$.1a(q,d)},X:9(e,p){6(!e)z 0;8 a=e.2l!=L?e[0]:e;6(p==\'1l\'&&$.2G.2F){8 b={\'1x\':\'1B\',\'3i\':\'2C\',\'1E\':\'1i\'},1G,1Z;$.2a(a,b,9(){1G=a.1t});b[\'1l\']=0;$.2a(a,b,9(){1Z=a.1t});z 1Z-1G}z r.K($.y(a,p))},K:9(v){v=2A(v);z 3o(v)?0:v}})})(3q);',62,213,'||||this|options|if|null|var|function||||||||||||||||||||list|size||||css|return|first|jcarousel|wrap|className|container|lt|last|clip|get|false|intval|undefined|tail|wh|length|locked|buttonPrev|addClass|px|dimension|buttonNext|inTail|vertical|margin|div|prevFirst|else|prevLast|callback|prev|animating|scroll|next|visible|circular|item|extend|each|timer|jcarouselstate|has|true|animate|initCallback|auto|clipping|Math|marginRight|break|for|while|parent|buttons|pos|fn|offsetWidth|li|disabled|before|display|removeClass|stopAuto|create|block|placeholder|reloadCallback|width|setup|oWidth|marginBottom|animation|buttonPrevCallback|attr|buttonNextCallback|ceil|notify|format|buttonPrevHTML|buttonNextHTML|both|scrollTail|remove|typeof|after|bind|offset|old|oWidth2|nodeName|0px|empty|start|reset|top|left|height|init|reload|swap|easing|offsetHeight|marginLeft|object|unbind|buttonPrevEvent|onAfterAnimation|startAuto|click|buttonNextEvent|jquery|children|marginTop|itemVisibleOutCallback|funcNext|itemVisibleInCallback|itemLastOutCallback|funcPrev|itemLastInCallback|prepend|itemFirstOutCallback|itemFirstInCallback|itemLoadCallback|lock|prototype|parseInt|resize|none|window|swing|safari|browser|defaults|round|index|borderBottomWidth|borderTopWidth|borderRightWidth|borderLeftWidth|normal|horizontal|jcarouselindex|isFunction|clearTimeout|1000|setTimeout|onBeforeAnimation|push|Aborting|loop|ol|infinite|an|ul|cause|will|This|items|set|No|jCarousel|alert|skin|indexOf|append|floor|html|split|new|float|string|parentNode|add|unlock|OL|isNaN|UL|jQuery'.split('|'),0,{}))

function CommentWidget( $element  ) {
	CommentWidget.baseConstructor.call(this, $element);	
	this.messageBox = new MessageBox($("#messageBox", $element));
}
/********************************************************************************/
CommentWidget.extend(Widget);


CommentWidget.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout),new SubscriptionEvent("lomLoaded", this.onLomLoaded)];	
}
/********************************************************************************/
// Tina: Wenn du direkt activate() als listener Methode subscriben willst,
// muesstest du aber die Parameter uebernehmen, und kannst mit _this auf das widget zugreifen.
CommentWidget.prototype.onLogin = function(type, args, _this) {
	console.log("onLogin " + type + ", user=" + args[0] + ", _this.id=" + _this.id);
	this.activate();
}
/********************************************************************************/
CommentWidget.prototype.onLogout = function(type, args, _this) {
	console.log("onLogout " + type + ", user=" + args[0] + ", _this.id=" + _this.id);
	this.deactivate();
}
/******************************/
CommentWidget.prototype.onLomLoaded = function(type, args, _this) {
	console.log(_this.id+"------------------------------onSelectLOM " + type + ", lom=" + args[0].id );
	this.selectedLOM = args[0].id
	this.selectedLOM_title=args[0].title;
	this.selectedLOM_description=args[0].description;
	this.selectedLOM_location=args[0].repositoryURL;
	this.selectedLOM_catalog=args[0].catalog.name;
	this.getComments();
}

/******************************/
CommentWidget.prototype.initInternal = function() {

	// server URLs
	this.get_commentServiceURL = MACEConstants.widgetsURL + "social/php/get_comments.php";
	this.add_commentServiceURL = MACEConstants.widgetsURL + "social/php/add_comment.php";
	
	// data values
	this.resourceBaseUrl="http://mace-project.eu/resource/";
	this.selectedLOM = null;
	

	// assign concrete elements
	this.comments_in =  $("#comments");
	this.comments_show = $("#popular_comments");
	
	this.comments_edit =  $("#comments_edit") ;
	
	this.popular_comments_header =  $("#popular_comments_header") ;
	

	this.my_comments =  $("#my_comments") ;
	this.controls = $("#comment_save_cancel");
	this.saveButton = $("#comments_saveButton");


    this.editButton = $("#comments_editButton");
	this.editButtonNotLogged = $("#comments_editButtonNotLogged");
    
	//this.comments_show.hide();
	
	var t = this;
	this.mode = "show";
	

	

	//make sure users only twitter
	this.comments_in.keydown(function(event){
		
		if(t.comments_in.val().length >140) {

			t.messageBox.showError("maximum of 140 characters", true);
			
			t.comments_in.val(t.comments_in.val().substring(0,140));
		}

	  
	});
	
	//clickhandler
	this.saveButton.click(function(){
	    if(t.comments_in.val() !="")
		    t.addComment();
		else{
		    t.comments_in.hide();
			t.controls.hide();	
			t.mode="show";
			t.activateEditButton();
			//track cancel action
			app.SessionTracker.saveAction("CommentWidget", "cancel","", t.selectedLOM);
		    
	    }
		    
		return false;
	});
	
	//
	this.comment_result=null;
  //  this.getComments();

	//
	this.comments_in.hide();
	this.controls.hide();	


	
	//
	console.log("CommentWidget ist da!---user:");

}
/******************************/

/******************************/
CommentWidget.prototype.getComments = function() {
	console.log("getComments");
	//cleanup
	this.popular_comments_header.html("Community Comments");
	this.comments_show.html('<div class="loadingMessage">Loading...</div>');

	
  	if(this.selectedLOM == null)
		this.resource_location=window.location.href; 
	else
		this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
	var t = this;
	var o={};
	o.bookmark=this.resource_location;

   $.getJSON(this.get_commentServiceURL, o,
   	function (comment_obj) {
		if(comment_obj.newResource){
			
			t.comments_show.html("<div class='incentiv' >no comments yet </div>	");
		}
   		else{
   		    t.comments_show.html("");
			t.comments_show.show();
			
			t.comment_result=comment_obj;
			var c_anzahl=t.comment_result.length;
			t.popular_comments_header.append(" &nbsp;("+c_anzahl+")")
	  
    		for (var i =0; i < comment_obj.length; i++) {
    		    var html=t.getCommentHTML(comment_obj[i]);
    			t.comments_show.append(html);
    		}


			
		}
   	});
}
/******************************/

CommentWidget.prototype.getCommentHTML = function(comment_obj) {

    var html='<div class="comment_loop clearfix"><div class="comment_user_icon" style="background-image:url('+comment_obj.usericon+')"> </div>';
	html +='<div class="comment_user_info"><a href="'+MACEConstants.rootURL+'user/'+comment_obj.nickname+'">'+comment_obj.username+'</a></div>';
	html +='<div class="comment_user_date">'+comment_obj.date+'</div>';
	html +='<div class="comment_txt clear">'+comment_obj.comment+'</div>';
	html +='</div>';
	return html;

}
/******************************/

CommentWidget.prototype.activate = function() {
	
	this.editButtonNotLogged.hide();
    this.editButton.show();
  
	var t=this;
	t.controls.hide();	

	
	if(this.mode == "edit"){
			t.comments_in.val("");
			t.my_comments.html("");
		   	t.comments_in.show();
	 	   	t.comments_in.focus();
		   	t.controls.show();
		   	
		    t.editButton.hide();
		    $("#CommentWidget").addClass("editMode");
	}
	else{
	    
	    this.activateEditButton();
    }



}
/******************************************/
CommentWidget.prototype.activateEditButton = function() {
  
    $("#CommentWidget").removeClass("editMode");
    this.editButton.show();
    var t=this;
	this.editButton.unbind( "click" );
	  this.editButton.bind("click",	
           	function() {

         		 t.comments_in.val("");
     			 t.my_comments.html("");
     		   	 t.comments_in.show();
     	 	   	 t.comments_in.focus();
     		   	 t.controls.show();
     		   	
        			$("#CommentWidget").addClass("editMode");
        			t.mode="edit";
        		   t.editButton.hide();
        	        
      });

    
    
}
/******************************/
CommentWidget.prototype.deactivate = function() {

    this.editButtonNotLogged.show();
    this.editButton.hide();
    this.editButtonNotLogged.bind("click",	
          	function() {

        		  	t.mode="edit";
        			app.loginComponent.open()

     });
     
	var t=this;
	this.mode="show";
	this.comments_in.hide();
	this.controls.hide();	
    $("#CommentWidget").removeClass("editMode");
}
/******************************/
CommentWidget.prototype.addComment = function() {
    
	if(this.selectedLOM == null)
		app.SessionTracker.saveAction("CommentWidget", "addComment",this.comments_in.val(), window.location.href);
	else
		app.SessionTracker.saveAction("CommentWidget", "addComment",this.comments_in.val(), this.selectedLOM);
	
	if(this.selectedLOM == null)
		this.resource_location=window.location.href; 
	else
		this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
	var t = this;
	var o={};
  	o.comment=this.comments_in.val();
	o.bookmark=	this.resource_location;		
	o.resource_title=this.selectedLOM_title;
	o.resource_description = this.selectedLOM_description;
	o.selectedLOM_location=this.selectedLOM_location;
	o.catalog = this.selectedLOM_catalog;
	o.userId=	app.loginComponent.user.id;
	
   $.getJSON(this.add_commentServiceURL, o,
   	function (result) {
   		if(result.success) {
   			console.log("Saved values for '" + t.resource_location + "' received.");
			
   		//	t.my_comments.html("<div class='my_comment' >"+o.comment+" </div>	");

			t.comments_in.hide();
			t.controls.hide();	
			t.mode="show";
			t.activateEditButton();

			//refresh
			t.getComments();
   		}
   		else {
   		
   		}
   	
   	}
   );
}


function RatingWidget($element) {
	RatingWidget.baseConstructor.call(this, $element);	
}
/********************************************************************************/
RatingWidget.extend(Widget);
/********************************************************************************/

RatingWidget.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout), new SubscriptionEvent("lomLoaded", this.onLomLoaded)];
}
/********************************************************************************/
RatingWidget.prototype.onLogin = function(type, args, me) {
	console.log("onLogin " + type + ", user=" + args[0] + ", me.id=" + me.id);
	this.activate();

	if(!this.individualRatingBuilt && this.selectedLOM != null){
	    this.build_individual_rating();
	    this.build_stars_toRate();
    }
}
/********************************************************************************/
RatingWidget.prototype.onLogout = function(type, args, me) {
	console.log("onLogout " + type + ", user=" + args[0] + ", me.id=" + me.id);
	this.deactivate();
}
/******************************/
RatingWidget.prototype.onLomLoaded = function(type, args, _this) {
	if(app.loginComponent.user )
	console.log(_this.id+"------------------------------onSelectLOM " + type + ", lom=" + args[0].id+", user="+app.loginComponent.user.id );
	this.selectedLOM=args[0].id;
	this.selectedLOM_title=args[0].title;
	this.selectedLOM_description=args[0].description;
	this.selectedLOM_catalog=args[0].catalog.name;
	this.selectedLOM_location=args[0].repositoryURL;
	this.build_popular_rating();
	if(app.loginComponent.user != null ){
		this.build_individual_rating();
		this.build_stars_toRate();
	}
	//cleanUp
	this.rating_msg.html("");
	
}
/******************************************/
RatingWidget.prototype.initInternal= function(){
	
	// server URLs
	this.get_ratingServiceURL = MACEConstants.widgetsURL + "social/php/get_rating.php";
	this.get_users_ratingServiceURL = MACEConstants.widgetsURL + "social/php/get_users_rating.php";
	this.do_ratingServiceURL =MACEConstants.widgetsURL + "social/php/rate.php";
	

	// data values
	this.resourceBaseUrl="http://mace-project.eu/resource/";
	this.empty_star_src = MACEConstants.widgetsURL + "social/img/star_empty.gif";
	this.filled_star_src = MACEConstants.widgetsURL + "social/img/star_filled.gif";
	this.dark_empty_star_src = MACEConstants.widgetsURL + "social/img/dark_star_empty.gif";
	this.dark_filled_star_src = MACEConstants.widgetsURL + "social/img/dark_star_filled.gif";

 
	this.selectedLOM = null;
	
	//
	this.rating="-1";

	// assign concrete elements

	this.do_rating =  $("#stars_empty_edit");
	this.show_personal_rating =  $("#stars_empty_show");
	this.show_rating =  $("#stars_filled");
	this.rating_msg =  $("#rating_msg");
	this.rating_stats =  $("#rating_stats");
	this.personal_rating =  $("#individual_ratingComponent");
	this.rating_huelle = $("#rating_huelle_edit");
	this.controls = $("#rating_save_cancel");
	this.saveButton = $("#rating_saveButton");

	this.editBar = $("#rating_editBar");

    this.editButton = $("#rating_editButton");
	this.editButtonNotLogged = $("#rating_editButtonNotLogged");
	//
    
	this.rating_huelle.hide();
	//clean up
    this.controls.hide();

	this.mode = "show";
	
	
	this.saveButton.bind("click",	
	 	function() {
	   			t.save_rating();
				return false;
	});
	
	
	//anzeigen
//   if(this.selectedLOM != null)
//   	this.build_popular_rating();

	
	
	var t=this;

    this.personal_rating.hide();
	
		console.log("RatingWidget---initialisiert------::");	
}
/******************************************/
RatingWidget.prototype.build_popular_rating= function(){
	
	//cleanUp
	this.show_rating.html("");
	this.rating_stats.html("");
	var t=this;
	
	if(this.selectedLOM == null)
		this.resource_location=window.location.href; 
	else
		this.resource_location=this.resourceBaseUrl+this.selectedLOM;
	
	var o={};
  	o.bookmark=this.resource_location;


  	$.getJSON(this.get_ratingServiceURL,o,function (rateObj) {
		
		t.show_rating.html("");
		if(rateObj.newResource || rateObj.rating==0){
			console.log("not rated yet");
			for(var i=0;i<5;i++){
				t.show_rating.append("<img src='"+t.empty_star_src+"' id='i_star_"+i+"'/>");
				
			}
		}
		else{
			for(var i=0;i<rateObj.rating;i++){
				t.show_rating.append("<img src='"+t.filled_star_src+"' />");
			}
		
			while(i < 5){
				t.show_rating.append("<img src='"+t.empty_star_src+"' id='i_star_"+i+"'/>");
				$("#i_star_"+i).css({visibility: 'hidden'});
				i++;
			}
			if(rateObj.rating >0){
				t.rating_stats.html("average: "+rateObj.exact+" ("+rateObj.votes+" votes)");
	
			}
			
		}
	
		
	});
}
/******************************************/
RatingWidget.prototype.deactivateRating= function(){
   
    this.editBar.hide();
    this.personal_rating.show();
}
/******************************************/
RatingWidget.prototype.build_individual_rating= function(){
console.log("RatingWidget----------------------------------------------------------------------build_individual_rating")

	var t=this;
	this.individualRatingBuilt=true;
	
	if(this.selectedLOM == null)
		this.resource_location=window.location.href; 
	else
		this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
	var o={};
  	o.bookmark=this.resource_location;//componentController.selectedLOM;
	o.users_ID=	app.loginComponent.user.id;
	$.getJSON(this.get_users_ratingServiceURL,o,function (rateObj) {
		
		if(rateObj.newResource || rateObj.rating==0){
				t.rated=true;
			    t.personal_rating.hide();
		
		}
		else{
		
			for(var i=0;i<rateObj.rating;i++){
				
				t.show_personal_rating.append("<img src='"+t.filled_star_src+"' class='stars'/>");
			}
			//already rated
			    t.deactivateRating();

	 	}
	 
		
	});

	
}
/******************************************/

/******************************************/
RatingWidget.prototype.rate= function(rating){
	console.log("rate--"+rating)
	
	//speichern
	this.rating=parseInt(rating)+1;
	this.do_rating.html("");
	for(var i=0;i<= rating;i++){
		this.do_rating.append("<img src='"+this.dark_filled_star_src+"' />");
	}

    $(".tipsy").remove();


}
/******************************************/
RatingWidget.prototype.showWhatRatingWouldBe= function(idx){

   for(var i=0;i <= 5;i++){
   	$("#star_"+i).attr({src: this.dark_empty_star_src});
   
   }
	for(var i=0;i <= idx;i++){
		$("#star_"+i).attr({src: this.dark_filled_star_src});
	
	}
}
/******************************************/
RatingWidget.prototype.refreshEmptyStars= function(idx){

	for(var i=1;i<idx;i++){
		$("#i_star_"+i).attr({src: this.empty_star_src});
	
	}
}
/******************************************/
RatingWidget.prototype.activate = function() {
    
    this.editButtonNotLogged.hide();
    this.editButton.show();
    this.activateEditButton();
    
    
	var t=this;
	
	this.personal_rating.unbind("click");

	if(this.mode == "edit"){
		this.build_stars_toRate();
		$("#RatingWidget").addClass("editMode");
		
		this.rating_huelle.show();
		this.controls.show();

        this.editButton.hide();
		
	}
	


	
}
/******************************************/
RatingWidget.prototype.activateEditButton = function() {
  
    $("#RatingWidget").removeClass("editMode");
   	
   var t=this;
	this.editButton.unbind( "click" );
	  this.editButton.bind("click",	
           	function() {

         		  	if(t.selectedLOM != null)
            		    t.build_individual_rating();
        			$("#RatingWidget").addClass("editMode");
        			t.mode="edit";
        			t.rating_huelle.show();
        			t.controls.show();
        	
        	        t.editButton.hide();
        	        
      });
    this.rating_huelle.hide();  
    this.controls.hide();
    this.editButton.show();
    
}
/******************************************/
RatingWidget.prototype.deactivate = function() {
	var t=this;



    this.editButtonNotLogged.show();
    this.editButton.hide();
    this.controls.hide();
    this.do_rating.hide();

    this.editButtonNotLogged.bind("click",	
          	function() {

        		  	t.mode="edit";
        			app.loginComponent.open()

     });
 
	$("#RatingWidget").removeClass("editMode");

}
/******************************************/
RatingWidget.prototype.build_stars_toRate = function() {
	
	var t=this;
	
	this.do_rating.show();	
	this.do_rating.html("");
	
	
	
	this.do_rating.unbind();
	this.do_rating.remove(".stars");
	for(var i=0;i<5;i++){
	    var tooltip="";
	    if(i==0)
	        tooltip=" class='showTooltip' title='Low quality'";
	    if(i==2)
	         tooltip=" class='showTooltip' title='Medium quality'";
	    if(i==4)
	         tooltip=" class='showTooltip' title='Like it a lot'";
		this.do_rating.append("<img src='"+t.dark_empty_star_src+"' id='star_"+i+"' "+tooltip+"/>");
	
	}
	$(".showTooltip").tipsy({fade: true, gravity:"s"});

   this.do_rating.bind("mouseover", function(e){ 
   	
	   	var star=e.target.id;
	   	if(star !="y"){
	    	 	var idx=star.substr(star.length-1,star.length);

	   	  		t.showWhatRatingWouldBe(idx);
	   	}
   
	 });
	
   this.do_rating.bind("click", function(e){ 
   
	   		var star=e.target.id;
		   	if(star !="y"){
		    	 	var idx=star.substr(star.length-1,star.length)
		   	  		t.rate(idx);
		   	}
   
	 });
    // this.saveButton.show();
}

/******************************************/
RatingWidget.prototype.save_rating = function() {

	console.log("save_rating--"+this.contentToTag+"---:"+this.rating);
  	var t = this;
  	
  	if(this.rating != -1){
        	if(t.selectedLOM == null)
        		app.SessionTracker.saveAction("RatingWidget", "rate",this.rating, window.location.href);
        	else
        		app.SessionTracker.saveAction("RatingWidget", "rate",this.rating, this.selectedLOM);


        	this.mode="show";


	
        	if(this.selectedLOM == null)
        		this.resource_location=window.location.href; 
        	else
        		this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
          	var o={};
          	o.bookmark =	this.resource_location;	
        	o.resource_title=this.selectedLOM_title;
        	o.resource_description = this.selectedLOM_description;
        	o.selectedLOM_location=this.selectedLOM_location;
        	o.catalog = this.selectedLOM_catalog;
            o.rating =this.rating;
        	o.users_ID=	app.loginComponent.user.id;

    
            $.getJSON(this.do_ratingServiceURL,o,function (r_feedback) {
            	t.rating_msg.html("");
            	t.rating_msg.removeClass("warning");
            	if(r_feedback.success==false){
            		t.rating_msg.addClass("warning");
            		t.rating_msg.html(r_feedback.message);
        			t.build_individual_rating();
            	}
            	else{
            	//	t.init();
        			t.personal_rating.bind("click",function(){ 

        				t.build_stars_toRate();
        			});
        			t.build_individual_rating();
        			t.build_popular_rating();
        			t.personal_rating.show();
        			t.editBar.hide();
            	}
    		
            });
        }// != -1
        else{
            	this.mode="show";
				this.activateEditButton();
			}
}

function TagWidget($element,mode) {
	TagWidget.baseConstructor.call(this, $element);	
	this.mode=mode;
}
/********************************************************************************/
TagWidget.extend(Widget);
/********************************************************************************/

TagWidget.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout), new SubscriptionEvent("lomLoaded", this.onLomLoaded)];
	// new SubscriptionEvent("searchResultSelect", this.onSelectLOM),
}
/********************************************************************************/
TagWidget.prototype.onLogin = function(type, args, me) {
	console.log("onLogin " + type + ", user=" + args[0] + ", me.id=" + me.id);

    

    
	this.activate();
	console.log(me.id+"---User----"+app.loginComponent.user.aloeSessionId);
			
}
/********************************************************************************/
TagWidget.prototype.onLogout = function(type, args, me) {
	console.log("onLogout " + type + ", user=" + args[0] + ", me.id=" + me.id);

        
	this.deactivate();
}
/******************************/
TagWidget.prototype.onLomLoaded = function(type, args, _this) {
	console.log(_this.id+"------------------------------onSelectLOM " + type + ", lom.location=" + args[0].repositoryURL );

	this.selectedLOM = args[0].id;
	this.selectedLOM_title=args[0].title;
	this.selectedLOM_description=args[0].description;
	this.selectedLOM_location=args[0].repositoryURL;
	this.selectedLOM_catalog=args[0].catalog.name;
	
	this.build_tagcloud();
	if(app.loginComponent.user != null )
		this.getUserTags();
}
/******************************/


/*********************************************************/
TagWidget.prototype.initInternal = function(){
	
	// server URLs
	this.get_tagsServiceURL = MACEConstants.widgetsURL + "social/php/get_tags_json.php";
	this.save_tagsServiceURL = MACEConstants.widgetsURL + "social/php/addtags.php";
	this.get_suggest_tagsServiceURL = MACEConstants.widgetsURL + "social/php/get_suggest_tags.php";
	
	// data values
	this.resourceBaseUrl="http://mace-project.eu/resource/";
	this.selectedLOM = null;


	// assign concrete elements
	this.tag_input =  $("#tags") ;

	this.tag_cloud =  $("#tagcloud") ;
	this.add_button =  $("#addTag_button") ;
	this.tag_input_container =  $("#tags_edit") ;

	
	this.personal_tags=$("#individual_taggingComponent") ;

	this.community_tags=$("#community_tags") ;
	
	this.my_tags_header =  $("#my_tags_header") ;
	this.my_tags =  $("#my_tags") ;
	this.controls = $("#tag_save_cancel");
	this.editButton = $("#editButton");
	this.editButtonNotLogged = $("#editButtonNotLogged");
	this.editButtonLabel = $("#editButtonLabel");
	this.saveButton = $("#tags_saveButton");
	this.cancelButton = $("#tags_cancelButton");
	this.deleteButton = $("#tags_deleteButton");

	
	this.added_tags_arr=[];
	this.editTag=null;
	console.log("TaggingWidget ist da-----");
	
	
	this.personal_tags.hide();

	var t = this;
	
	this.my_tags_arr=new Array();
	this.mode="show";

	
	
	
	
	this.tag_input.hide();
	this.controls.hide();
	this.cancelButton.hide();
	
	
	this.saveButton.bind("click",	
	 	function() {
	   			t.save_tags();
				return false;
		
	});
	this.cancelButton.unbind("click");
	this.cancelButton.bind("click",	
	 	function() {
			
				t.mode="show";
				t.tag_input.hide();
				t.controls.hide();
				t.cancelButton.hide();
				t.editButton.show();
				t.activateEditButton();
				
				if(t.selectedLOM == null)
					app.SessionTracker.saveAction("TaggingWidget", "cancel","", window.location.href);
				else
					app.SessionTracker.saveAction("TaggingWidget", "cancel","", t.selectedLOM);
					
				//deleteicon loeschen
				$.each(t.my_tags_arr, function(i,obj) {
						
					$("#deleteTag_"+i).html("");
				});
				t.fillin_my_tags();
				return false;
	
	});
	this.deleteButton.bind("click",	
	 	function() {
	
				t.deleteTags();
				return false;
	});
	
	//counter for tags
	this.taggs_added=0;
	///submit on Enter
	this.tag_input.keydown(function(event){
		
		if(event.keyCode ==13) {

		//	t.addToMyTags();
			t.save_tags();
		
			t.showControls();
		}
	  if(event.keyCode ==32 ) {
	  	t.showControls();
	  }
	});
	
	
}
/******************************************/
TagWidget.prototype.init_suggestions = function(){

	
	var w=this.tag_input.width();
	var t = this;

	//selectedtag flag
	this.tags_EnterFlag=false;

	this.tag_input.autocomplete(this.suggest_tags, {

		width: (w+6),
		minChars: 1,
		matchContains: false,
		max: 50,
		widget: "TagWidget",
		
	// formatMatch: matching / searching
			formatMatch: function(data, i, n, value) {
				return data.tag;
			},
			// formatItem: show in list
			formatItem: function(data, i, n, value) {
				return data.tag +"("+data.freq+")";
			},
			// formatResult: show in textbox
			formatResult: function(data, value) {
				return data.tag;
			}

	});
	this.tag_input.bind("result", function(event, data, formatted) {

		console.log("result!!!!!!!!!!!!!!");
		if(t.tags_EnterFlag == false)
			t.tags_EnterFlag=true;
			t.showControls();
	});
}
/******************************************/
TagWidget.prototype.showControls = function() {
	
	

	this.controls.show();
	this.cancelButton.show();
}

/******************************************/
TagWidget.prototype.activate = function() {
//	console.log("TagWidget----------------------------------------------------activate!---"+this.mode);

   
   
    this.editButtonNotLogged.hide();
    this.editButton.show();
    
	var t=this;
	var o={};
	o.user_id=app.loginComponent.user.id;	
	   	$.getJSON(this.get_suggest_tagsServiceURL,o,function (suggest_tags) {
	   		t.suggest_tags = suggest_tags;
	   		t.init_suggestions();
	   	});
	this.tag_input.hide();
	this.activateEditButton();
	
   if(this.mode =="edit"){
		if(this.my_tags_arr.length == 0)
			this.my_tags.html("");
		this.tag_input.show();
		this.tag_input.focus();
		this.showControls()
   	    this.editButton.hide();
   }
   else{
   	   this.tag_input.hide();
       this.editButton.show();
    
   }


   
  
	 
	if(t.selectedLOM != null)
		this.getUserTags();
	

}

/******************************************/
TagWidget.prototype.activateEditButton = function() {
   console.log("TagWidget--------------------------------------------------------------------------------------------activateEditButton:"+this.mode);
   
   	$("#TagWidget").removeClass("editMode");
   	
   var t=this;
	this.editButton.unbind( "click" );
	  this.editButton.bind("click",	
           	function() {

         		  	if(t.mode!="edit" ){  
        				if(t.my_tags_arr.length ==0)
        					t.my_tags.html("");
        				t.tag_input.show();
        				t.tag_input.focus();
        				t.showControls();
        				t.editButton.hide();

        				t.personal_tags.show();
        				t.fillin_my_tags("active");

        			}
        			t.mode="edit";
        			$("#TagWidget").addClass("editMode");
        	 
      });
}
/******************************************/
TagWidget.prototype.deactivate = function() {
//console.log("TagWidget----------deactivate!---uid:");
	var t=this;
	this.mode="show";
    this.editButtonNotLogged.show();
    this.editButton.hide();
    
    
	 this.editButtonNotLogged.bind("click",	
          	function() {

        		  	t.mode="edit";
        			app.loginComponent.open()

     });
     
     this.personal_tags.hide();

     
	this.tag_input.hide();
	this.controls.hide();
	this.cancelButton.hide();
	$("#TagWidget").removeClass("editMode");
	
}
/******************************************/
TagWidget.prototype.addToMyTags= function(){
		
		var t=this;

		
		
		var tags=$.trim(this.tag_input.val().replace("&nbsp;"," "));
//	console.log("addToMyTags----"+this.tag_input.val());	
		this.tag_input.val("");
		
			if(tags.indexOf(" ") >0){
				var tmp=tags.split(" ");
				for(i=0;i<tmp.length;i++){
			
					var flag=false;
					for(j=0;j<this.my_tags_arr.length;j++){
						if(this.my_tags_arr[j] == tmp[i])
							flag=true;
					}
					this.my_tags_arr.push(tmp[i]);
				
					this.fillin_my_tags("active");
				}	
			}
			else{
					var flag=false;
					for(j=0;j<this.my_tags_arr.length;j++){
						if(this.my_tags_arr[j] == tags)
							flag=true;
					}
					if(tags !="")
						this.my_tags_arr.push(tags);
						
					this.fillin_my_tags("active");
					
			}
			this.tags_EnterFlag=true;
		//}
}

/******************************************/
TagWidget.prototype.fillin_my_tags= function(mode){
    //console.log("TagWidget-------------------------------------------------------------------------------------------------------fillin_my_tags-:"+mode);

	var t=this;
	this.my_tags.html("");
	if(this.my_tags_arr.length == 0){
	    this.personal_tags.hide();
    }
    else{
	    $.each(this.my_tags_arr, function(i,obj) {
		
	    
    		if(mode=="active"){
    		    t.my_tags.append("<li id='add_"+i+"'style='float:left;'><a id='added_tag"+i+"' class='editableLink showTooltip' title='Click to edit this tag'>"+t.my_tags_arr[i]+" </a><div id='deleteTag_"+i+"' style='float:left;'/></li> ");
    			//hover
    			  $("#added_tag"+i).hover(
    			  	function() {	
					
    						$(this).addClass("editableLink_hover");	
				
    				},
    			  	function() {
				
    			  			$(this).removeClass("editableLink_hover");
    			  			$(this).addClass("editableLink");
			
    			  	});
    			  //click
    			  $("#added_tag"+i).bind("click",	function() {
				    
    				    $(".tipsy").remove();
    			  		var this_tag=$.trim($(this).html());
    				  	t.tag_input.val(this_tag);
    					t.tag_input.show();
    					t.editButton.hide();
    					t.tag_input.focus();
    					t.showControls();
    					t.editTag=this_tag;
    				  	//remove from my_tags
    				  	var shift_arr=$.grep( t.my_tags_arr, function(i){ 
    					  	if(i != this_tag){
    							return i;
    					}
    					 });
    					t.my_tags_arr=shift_arr;
    				  	t.fillin_my_tags("active");
			  
    			  });
    			  //delete icon adden
    		
    					$("#deleteTag_"+i).html('<a class="clearButton showTooltip" id="deleteTagIcon_'+i+'" title="Click to remove tag '+obj+'"/>');
    					$("#deleteTagIcon_"+i).click(function(){
    					    $(".tipsy").remove();
    				   		t.editTag=obj;

    				   	  	//remove from my_tags
    				   	  	var shift_arr=$.grep( t.my_tags_arr, function(i){ 
    				   		  	if(i != t.editTag){
    				   				return i;
    				   		}
    				   		 });
    				   		t.my_tags_arr=shift_arr;
    				   	  	t.fillin_my_tags("active");
    				   		t.deleteTags();
    				   	});

    		}
    		else{ //nur anzeige
    		    t.my_tags.append("<li id='add_"+i+"'style='float:left;'><a href='"+MACEConstants.rootURL+"tag#/?query=searchTerm,"+t.my_tags_arr[i]+"' id='added_tag"+i+"' class='editableLink showTooltip' title='Click to find more contents with tag "+t.my_tags_arr[i]+"' target='_blank'>"+t.my_tags_arr[i]+" </a><div id='deleteTag_"+i+"' style='float:left;'/></li> ");
		    
    			  //hoover weg
    			  $("#added_tag"+i).hover();
    	    }
		
    	});//each
    	$(".showTooltip").tipsy({fade: true, gravity:"s"});
    	this.personal_tags.show();
	}
}
/******************************************/
TagWidget.prototype.getUserTags= function(){
	
	//console.log("TagWidget-------------------------------------------------------------------------------------------------------getUserTags-:");
	
	this.my_tags_saved_arr=new Array();
	this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
  	var o={};
  	o.bookmark=this.resource_location;
	o.user_id=app.loginComponent.user.id;
	var t = this;
    this.personal_tags.show();
	
  	$.getJSON(this.get_tagsServiceURL,o,function (tagObj) {
		if(tagObj.newResource || tagObj[0].name == ""){
		
			t.personal_tags.hide();
		}
		else{
			t.personal_tags.show();	
			t.my_tags.html("");
			$.each(tagObj, function(i,obj) {
				//keep track of my tags
				t.my_tags_arr.push(tagObj[i].name);
				t.my_tags_saved_arr.push(tagObj[i].name);
				
				t.fillin_my_tags();
				
	
			});//each
		
		}
		

	});
}
/******************************************/
TagWidget.prototype.build_tagcloud= function(){
	

	this.resource_location=this.resourceBaseUrl+this.selectedLOM;
		
  	var o={};
	o.bookmark=this.resource_location;

	var t = this;

  	$.getJSON(this.get_tagsServiceURL,o,function (tagObj) {
		t.tag_cloud.html("");
		if(tagObj.newResource || tagObj[0].name == null || tagObj[0].name == ""){
			console.log("no tags yet");
			t.tag_cloud.append("<div class='incentiv' >no tags yet </div>");
		}
		else{
			$.each(tagObj, function(i,obj) {
				if(tagObj[i].name != ""){
					t.tag_cloud.append(	"<li><a  href='"+MACEConstants.rootURL+"tag#/?query=searchTerm,"+tagObj[i].name+"' class='"+tagObj[i].stil+" showTooltip' title='Click to find more contents with tag "+tagObj[i].name+"' target='_blank'> "+tagObj[i].name+"</a></li>");
					t.tag_input.val("");
				}
			});
		}
	});
}
/******************************************/

in_array = function ( check ,arr) {
	var len = arr.length;
	for ( var x = 0 ; x <= len ; x++ ) {
		if ( arr[x] == check ) return true;
	}
	return false;
}
/******************************************/
TagWidget.prototype.save_tags= function(){

	//track
	sb=this.tag_input.val();
	while(sb.indexOf(" ") >0)
	    sb=sb.replace(" ",",");

	if(sb.indexOf(",")>0){
	    sbArr=sb.split(",");
	    for(i=0;i<sbArr.length;i++)
	        	app.SessionTracker.saveAction("TaggingWidget", "addtags",sbArr[i], this.selectedLOM);
    }
	else
		app.SessionTracker.saveAction("TaggingWidget", "addtags",sb, this.selectedLOM);
		

	
	var t = this;
	
	
	
	
//	console.log("save_tags----------------editTag"+this.editTag);	
  		if(this.editTag != null){
			var o={};
			o.bookmark=this.resource_location;
		  	o.editTagOld=this.editTag;
			o.editTagNew=this.tag_input.val().replace("&nbsp;"," ");
//	console.log("save_tags-------------------editTagNew"+o.editTagNew);	

		   $.getJSON(this.save_tagsServiceURL,o,function (result) {
		   		if(result.success){

			   		console.log("saved");
			   		t.editTag =null;
			   	}

		   });
		}
		else{
	        var tags=$.trim(this.tag_input.val().replace("&nbsp;"," "));

	        this.resource_location=this.resourceBaseUrl+this.selectedLOM;
	
          	var o={};
          	o.bookmark=this.resource_location;
        	o.resource_title=this.selectedLOM_title;
        	o.resource_description = this.selectedLOM_description;
        	o.selectedLOM_location=this.selectedLOM_location;
        	o.catalog = this.selectedLOM_catalog;
        	//o.tags=this.my_tags_arr.join(" ");
        	o.tags=tags;

	
	
           $.getJSON(this.save_tagsServiceURL,o,function (result) {

           		if(result.success){

        	   		t.build_tagcloud();
        	   		console.log("saved");
        	   	}
        	   	else{
        	   		t.tag_input.val(result.msg);
        	   	}
           });
       }
       
       this.addToMyTags();
}
/******************************************/
TagWidget.prototype.deleteTags= function(){
//	console.log("deleteTags---"+this.editTag);
	if(this.selectedLOM == null)
		app.SessionTracker.saveAction("TaggingWidget", "deleteTags",this.editTag, window.location.href);
	else
		app.SessionTracker.saveAction("TaggingWidget", "deleteTags",this.editTag, this.selectedLOM);
		

	var t = this;
	
	if(this.editTag != null){
		var o={};
	  	o.tagToDelelete=this.editTag;
		o.bookmark=this.resource_location;
	
		$.getJSON(this.save_tagsServiceURL,o,function (result) {

		   		if(result.success){
		
				    t.build_tagcloud();
			   		console.log("saved");
			   	}
			   	else{
			   		t.tag_input.val(result.msg);
			   	}
		   });
		}
 }
	
	
   

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

/* SWFObject v2.1 <http://code.google.com/p/swfobject/>
	Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
	This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
*/
var swfobject=function(){var b="undefined",Q="object",n="Shockwave Flash",p="ShockwaveFlash.ShockwaveFlash",P="application/x-shockwave-flash",m="SWFObjectExprInst",j=window,K=document,T=navigator,o=[],N=[],i=[],d=[],J,Z=null,M=null,l=null,e=false,A=false;var h=function(){var v=typeof K.getElementById!=b&&typeof K.getElementsByTagName!=b&&typeof K.createElement!=b,AC=[0,0,0],x=null;if(typeof T.plugins!=b&&typeof T.plugins[n]==Q){x=T.plugins[n].description;if(x&&!(typeof T.mimeTypes!=b&&T.mimeTypes[P]&&!T.mimeTypes[P].enabledPlugin)){x=x.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AC[0]=parseInt(x.replace(/^(.*)\..*$/,"$1"),10);AC[1]=parseInt(x.replace(/^.*\.(.*)\s.*$/,"$1"),10);AC[2]=/r/.test(x)?parseInt(x.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof j.ActiveXObject!=b){var y=null,AB=false;try{y=new ActiveXObject(p+".7")}catch(t){try{y=new ActiveXObject(p+".6");AC=[6,0,21];y.AllowScriptAccess="always"}catch(t){if(AC[0]==6){AB=true}}if(!AB){try{y=new ActiveXObject(p)}catch(t){}}}if(!AB&&y){try{x=y.GetVariable("$version");if(x){x=x.split(" ")[1].split(",");AC=[parseInt(x[0],10),parseInt(x[1],10),parseInt(x[2],10)]}}catch(t){}}}}var AD=T.userAgent.toLowerCase(),r=T.platform.toLowerCase(),AA=/webkit/.test(AD)?parseFloat(AD.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,q=false,z=r?/win/.test(r):/win/.test(AD),w=r?/mac/.test(r):/mac/.test(AD);/*@cc_on q=true;@if(@_win32)z=true;@elif(@_mac)w=true;@end@*/return{w3cdom:v,pv:AC,webkit:AA,ie:q,win:z,mac:w}}();var L=function(){if(!h.w3cdom){return }f(H);if(h.ie&&h.win){try{K.write("<script id=__ie_ondomload defer=true src=//:><\/script>");J=C("__ie_ondomload");if(J){I(J,"onreadystatechange",S)}}catch(q){}}if(h.webkit&&typeof K.readyState!=b){Z=setInterval(function(){if(/loaded|complete/.test(K.readyState)){E()}},10)}if(typeof K.addEventListener!=b){K.addEventListener("DOMContentLoaded",E,null)}R(E)}();function S(){if(J.readyState=="complete"){J.parentNode.removeChild(J);E()}}function E(){if(e){return }if(h.ie&&h.win){var v=a("span");try{var u=K.getElementsByTagName("body")[0].appendChild(v);u.parentNode.removeChild(u)}catch(w){return }}e=true;if(Z){clearInterval(Z);Z=null}var q=o.length;for(var r=0;r<q;r++){o[r]()}}function f(q){if(e){q()}else{o[o.length]=q}}function R(r){if(typeof j.addEventListener!=b){j.addEventListener("load",r,false)}else{if(typeof K.addEventListener!=b){K.addEventListener("load",r,false)}else{if(typeof j.attachEvent!=b){I(j,"onload",r)}else{if(typeof j.onload=="function"){var q=j.onload;j.onload=function(){q();r()}}else{j.onload=r}}}}}function H(){var t=N.length;for(var q=0;q<t;q++){var u=N[q].id;if(h.pv[0]>0){var r=C(u);if(r){N[q].width=r.getAttribute("width")?r.getAttribute("width"):"0";N[q].height=r.getAttribute("height")?r.getAttribute("height"):"0";if(c(N[q].swfVersion)){if(h.webkit&&h.webkit<312){Y(r)}W(u,true)}else{if(N[q].expressInstall&&!A&&c("6.0.65")&&(h.win||h.mac)){k(N[q])}else{O(r)}}}}else{W(u,true)}}}function Y(t){var q=t.getElementsByTagName(Q)[0];if(q){var w=a("embed"),y=q.attributes;if(y){var v=y.length;for(var u=0;u<v;u++){if(y[u].nodeName=="DATA"){w.setAttribute("src",y[u].nodeValue)}else{w.setAttribute(y[u].nodeName,y[u].nodeValue)}}}var x=q.childNodes;if(x){var z=x.length;for(var r=0;r<z;r++){if(x[r].nodeType==1&&x[r].nodeName=="PARAM"){w.setAttribute(x[r].getAttribute("name"),x[r].getAttribute("value"))}}}t.parentNode.replaceChild(w,t)}}function k(w){A=true;var u=C(w.id);if(u){if(w.altContentId){var y=C(w.altContentId);if(y){M=y;l=w.altContentId}}else{M=G(u)}if(!(/%$/.test(w.width))&&parseInt(w.width,10)<310){w.width="310"}if(!(/%$/.test(w.height))&&parseInt(w.height,10)<137){w.height="137"}K.title=K.title.slice(0,47)+" - Flash Player Installation";var z=h.ie&&h.win?"ActiveX":"PlugIn",q=K.title,r="MMredirectURL="+j.location+"&MMplayerType="+z+"&MMdoctitle="+q,x=w.id;if(h.ie&&h.win&&u.readyState!=4){var t=a("div");x+="SWFObjectNew";t.setAttribute("id",x);u.parentNode.insertBefore(t,u);u.style.display="none";var v=function(){u.parentNode.removeChild(u)};I(j,"onload",v)}U({data:w.expressInstall,id:m,width:w.width,height:w.height},{flashvars:r},x)}}function O(t){if(h.ie&&h.win&&t.readyState!=4){var r=a("div");t.parentNode.insertBefore(r,t);r.parentNode.replaceChild(G(t),r);t.style.display="none";var q=function(){t.parentNode.removeChild(t)};I(j,"onload",q)}else{t.parentNode.replaceChild(G(t),t)}}function G(v){var u=a("div");if(h.win&&h.ie){u.innerHTML=v.innerHTML}else{var r=v.getElementsByTagName(Q)[0];if(r){var w=r.childNodes;if(w){var q=w.length;for(var t=0;t<q;t++){if(!(w[t].nodeType==1&&w[t].nodeName=="PARAM")&&!(w[t].nodeType==8)){u.appendChild(w[t].cloneNode(true))}}}}}return u}function U(AG,AE,t){var q,v=C(t);if(v){if(typeof AG.id==b){AG.id=t}if(h.ie&&h.win){var AF="";for(var AB in AG){if(AG[AB]!=Object.prototype[AB]){if(AB.toLowerCase()=="data"){AE.movie=AG[AB]}else{if(AB.toLowerCase()=="styleclass"){AF+=' class="'+AG[AB]+'"'}else{if(AB.toLowerCase()!="classid"){AF+=" "+AB+'="'+AG[AB]+'"'}}}}}var AD="";for(var AA in AE){if(AE[AA]!=Object.prototype[AA]){AD+='<param name="'+AA+'" value="'+AE[AA]+'" />'}}v.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AF+">"+AD+"</object>";i[i.length]=AG.id;q=C(AG.id)}else{if(h.webkit&&h.webkit<312){var AC=a("embed");AC.setAttribute("type",P);for(var z in AG){if(AG[z]!=Object.prototype[z]){if(z.toLowerCase()=="data"){AC.setAttribute("src",AG[z])}else{if(z.toLowerCase()=="styleclass"){AC.setAttribute("class",AG[z])}else{if(z.toLowerCase()!="classid"){AC.setAttribute(z,AG[z])}}}}}for(var y in AE){if(AE[y]!=Object.prototype[y]){if(y.toLowerCase()!="movie"){AC.setAttribute(y,AE[y])}}}v.parentNode.replaceChild(AC,v);q=AC}else{var u=a(Q);u.setAttribute("type",P);for(var x in AG){if(AG[x]!=Object.prototype[x]){if(x.toLowerCase()=="styleclass"){u.setAttribute("class",AG[x])}else{if(x.toLowerCase()!="classid"){u.setAttribute(x,AG[x])}}}}for(var w in AE){if(AE[w]!=Object.prototype[w]&&w.toLowerCase()!="movie"){F(u,w,AE[w])}}v.parentNode.replaceChild(u,v);q=u}}}return q}function F(t,q,r){var u=a("param");u.setAttribute("name",q);u.setAttribute("value",r);t.appendChild(u)}function X(r){var q=C(r);if(q&&(q.nodeName=="OBJECT"||q.nodeName=="EMBED")){if(h.ie&&h.win){if(q.readyState==4){B(r)}else{j.attachEvent("onload",function(){B(r)})}}else{q.parentNode.removeChild(q)}}}function B(t){var r=C(t);if(r){for(var q in r){if(typeof r[q]=="function"){r[q]=null}}r.parentNode.removeChild(r)}}function C(t){var q=null;try{q=K.getElementById(t)}catch(r){}return q}function a(q){return K.createElement(q)}function I(t,q,r){t.attachEvent(q,r);d[d.length]=[t,q,r]}function c(t){var r=h.pv,q=t.split(".");q[0]=parseInt(q[0],10);q[1]=parseInt(q[1],10)||0;q[2]=parseInt(q[2],10)||0;return(r[0]>q[0]||(r[0]==q[0]&&r[1]>q[1])||(r[0]==q[0]&&r[1]==q[1]&&r[2]>=q[2]))?true:false}function V(v,r){if(h.ie&&h.mac){return }var u=K.getElementsByTagName("head")[0],t=a("style");t.setAttribute("type","text/css");t.setAttribute("media","screen");if(!(h.ie&&h.win)&&typeof K.createTextNode!=b){t.appendChild(K.createTextNode(v+" {"+r+"}"))}u.appendChild(t);if(h.ie&&h.win&&typeof K.styleSheets!=b&&K.styleSheets.length>0){var q=K.styleSheets[K.styleSheets.length-1];if(typeof q.addRule==Q){q.addRule(v,r)}}}function W(t,q){var r=q?"visible":"hidden";if(e&&C(t)){C(t).style.visibility=r}else{V("#"+t,"visibility:"+r)}}function g(s){var r=/[\\\"<>\.;]/;var q=r.exec(s)!=null;return q?encodeURIComponent(s):s}var D=function(){if(h.ie&&h.win){window.attachEvent("onunload",function(){var w=d.length;for(var v=0;v<w;v++){d[v][0].detachEvent(d[v][1],d[v][2])}var t=i.length;for(var u=0;u<t;u++){X(i[u])}for(var r in h){h[r]=null}h=null;for(var q in swfobject){swfobject[q]=null}swfobject=null})}}();return{registerObject:function(u,q,t){if(!h.w3cdom||!u||!q){return }var r={};r.id=u;r.swfVersion=q;r.expressInstall=t?t:false;N[N.length]=r;W(u,false)},getObjectById:function(v){var q=null;if(h.w3cdom){var t=C(v);if(t){var u=t.getElementsByTagName(Q)[0];if(!u||(u&&typeof t.SetVariable!=b)){q=t}else{if(typeof u.SetVariable!=b){q=u}}}}return q},embedSWF:function(x,AE,AB,AD,q,w,r,z,AC){if(!h.w3cdom||!x||!AE||!AB||!AD||!q){return }AB+="";AD+="";if(c(q)){W(AE,false);var AA={};if(AC&&typeof AC===Q){for(var v in AC){if(AC[v]!=Object.prototype[v]){AA[v]=AC[v]}}}AA.data=x;AA.width=AB;AA.height=AD;var y={};if(z&&typeof z===Q){for(var u in z){if(z[u]!=Object.prototype[u]){y[u]=z[u]}}}if(r&&typeof r===Q){for(var t in r){if(r[t]!=Object.prototype[t]){if(typeof y.flashvars!=b){y.flashvars+="&"+t+"="+r[t]}else{y.flashvars=t+"="+r[t]}}}}f(function(){U(AA,y,AE);if(AA.id==AE){W(AE,true)}})}else{if(w&&!A&&c("6.0.65")&&(h.win||h.mac)){A=true;W(AE,false);f(function(){var AF={};AF.id=AF.altContentId=AE;AF.width=AB;AF.height=AD;AF.expressInstall=w;k(AF)})}}},getFlashPlayerVersion:function(){return{major:h.pv[0],minor:h.pv[1],release:h.pv[2]}},hasFlashPlayerVersion:c,createSWF:function(t,r,q){if(h.w3cdom){return U(t,r,q)}else{return undefined}},removeSWF:function(q){if(h.w3cdom){X(q)}},createCSS:function(r,q){if(h.w3cdom){V(r,q)}},addDomLoadEvent:f,addLoadEvent:R,getQueryParamValue:function(v){var u=K.location.search||K.location.hash;if(v==null){return g(u)}if(u){var t=u.substring(1).split("&");for(var r=0;r<t.length;r++){if(t[r].substring(0,t[r].indexOf("="))==v){return g(t[r].substring((t[r].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(A&&M){var q=C(m);if(q){q.parentNode.replaceChild(M,q);if(l){W(l,true);if(h.ie&&h.win){M.style.display="block"}}M=null;l=null;A=false}}}}}();
/**
 * Competence Widget
	Embeds a flash/flex widget and passes LOMXML and contentId
 * 
 * @author mo
 */

function CompetenceWidget($element) {
	CompetenceWidget.baseConstructor.call(this, $element);
	this.$element=$element;
	
	this.userId = null;
	this.contentId = null;
}
CompetenceWidget.extend(Widget);

// NB: There are two versions: live and test (see mace.constants.js)
CompetenceWidget.SWF_URL = MACEConstants.competenceFlashURL;

CompetenceWidget.prototype.getSubscriptionEvents = function() {
	console.log(this.id + " .getSubscriptionEvents()");
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout),
		new SubscriptionEvent("lomLoaded", this.onLomLoaded)];
}

CompetenceWidget.prototype.onLogin = function(type, args) {
	console.log("onLogin " + type + ", user=" + args[0] + ", this.id=" + this.id);
	this.userId = args[0].id;
	this.updateFlash();
}

CompetenceWidget.prototype.onLogout = function(type, args) {
	console.log("onLogout " + type + ", user=" + args[0] + ", this.id=" + this.id);
	this.userId = null;
	this.updateFlash();
}

CompetenceWidget.prototype.onLomLoaded = function(type, args) {
	var $lom=args[0];
	this.contentId = $lom.id;
	this.updateFlash();
}

CompetenceWidget.prototype.updateFlash = function() {
	/*
	var LOMXML="<classificationNodes>";
	$("classification", $lom.$xml).each(function(){
		LOMXML+=$(this).html();
	});
	LOMXML+="</classificationNodes>";
	*/
	
	// TODO 
	var flashvars={
		contentId: this.contentId,
		userId: this.userId
	};
	
	var params = {
		menu: "false",
		salign:"TL",
		scale:"noscale",
		allowScriptAccess:"always",
		wmode:"opaque"		
	};
	
	var attributes = {
	};
	
	console.log("CompetenceWidget.updateFlash: contentID=" + this.contentId + ", userID="+ this.userId);
	swfobject.embedSWF(CompetenceWidget.SWF_URL, "flashContainer", "290", "350", "9.0.0", MACEConstants.rootURL+"js/flashInstall/expressInstall.swf", flashvars, params, attributes);	
}

CompetenceWidget.prototype.setHeight = function(height) {
	this.$element.height(height);
}

/**
 * Creates an Entry with given params but without children.
 * 
 * A single entry created from the vocabulary, could  be either
 * a category and/or a value
 * 
 * @param {String} id Id of this entry
 * @param {String|Array} labels The label or labels of this entry.
 * @param {Entry} parent The parent of this entry. Can be null.
 * @param {boolean} selectable If this entry is selectable for one LOM or only for taxonomy structuring.
 * @param {String} description The description. (optional: if not specified it will be generated from labels)
 */
function Entry(id, labels, parent, selectable, description) {
	// Identifier
	this.id = id;
	
	// References to related hierarchical entries 
	this.parent = parent;
	this.children = [];
	this.facet = null;
	this.group = null;
	
	// Flag whether users are able to use this entry. 
	this.selectable = selectable;
	
	
	// Label(s) and description
	this.label = "n/a";
	this.labels = null;
	this.description = null;
	
	if (!description) {
		this.description = description;
	}
	
	if (typeof labels == 'string') {
		this.label = labels;
		this.labels = null;
	}
	else if (typeof labels == 'object') {
		// First default language label is the main label
		this.label = labels.get(Entry.DEFAULT_LANG)[0];
		this.labels = labels;
		
		if (!this.description) {
			this.description = this._getDescriptionFromLabels();
		}
	}
}

// Levels of the taxonomy hierarchy
Entry.ROOT_LEVEL = 0;
Entry.GROUP_LEVEL = 1;
Entry.FACET_LEVEL = 2;
// items still can be hierarchical
Entry.ITEM_LEVEL = 3; 

/** The string to use as separator between hierarchical levels. Used in the full label. */
// NB: If separator is HTML entity '&gt;' the display is only correct if highlight of the autocomplete plugin is used. 
Entry.HIERARCHY_SEPARATOR = " » ";

/** The default language to use. If multiple default lang labels exist, the first one is used. */
Entry.DEFAULT_LANG = "en";

/**
 * Creates a nicely formatted description text from the synonym and localized labels. 
 */
Entry.prototype._getDescriptionFromLabels = function() {
	var description = "";
	
	var languages = this.labels.getKeys();
	for (var i = 0; i < languages.length; i++) {
		var lang = languages[i];
		var values = this.labels.get(lang);
		for (var j = 0; j < values.length; j++) {
			// Does not use first default lang label, as that's already the main this.label
			if (!(lang == Entry.DEFAULT_LANG && j == 0)) {
				var value = values[j];
				description += value + " (" + lang + "), ";
			} 
		}
	}
	// Remove last comma
	description = description.substring(0, description.length - 2);
	return description;
}

/**
 * Gets the full hierarchical label of this entry.
 * Includes all visible ancestors, i.e. without groups and root.
 *  
 * @param {String} The full label.
 */
Entry.prototype.getFullLabel = function() {
	var fullLabel = "";
	if (this.parent != null && Entry.isVisibleEntry(this.parent.id)) {
		fullLabel = this.parent.getFullLabel() + Entry.HIERARCHY_SEPARATOR;
	}
	fullLabel += this.label;
	return fullLabel;
}

/**
 * Returns the facet of this entry.
 */
Entry.prototype.getFacet = function() {
	if (this.facet == null) {
		this.facet = this._getAncestor(Entry.FACET_LEVEL);
	}
	return this.facet;
}

/**
 * Returns the group of this entry.
 */
Entry.prototype.getGroup = function() {
	if (this.group == null) {
		this.group = this._getAncestor(Entry.GROUP_LEVEL);
	}
	return this.group;
}

Entry.prototype._getAncestor = function(levelOfAncestor) {
	var p = this.parent;
	var level = 0;
	while (p.parent != null) {
		p = p.parent;
		level++;
	}
	p = this.parent;
	while (level > levelOfAncestor) {
		p = p.parent;
		level--;
	}
	return p;
}


Entry.prototype.toString = function() {
	return this.id + " (" + this.label + ")";
}

Entry.prototype.toLongString = function() {
	return "Entry \"" + this.id + "\" label=" + this.label + ", desc=" + this.description;
}

Entry.prototype.toJSON = function() {
	return '{"termId":"' + this.id + '",' + 
		'"term":"' + encodeURIComponent(this.label) + '",' +
		'"purpose":"' + encodeURIComponent(this.getGroup().label) + '",' +
		'"source":"MACE ' + encodeURIComponent(this.getFacet().label) + ' Catalog"}';
}

/**
 * Indicates whether this entry has children.
 * 
 * @return true if children list is not empty.
 */
Entry.prototype.hasChildren = function() {
	return this.children.length > 0;
}

/**
 * Adds a child to this entry. Additionally, its parent is set to this entry.
 * @param {Entry} child The child entry.
 */
Entry.prototype.addChild = function(child) {
	child.setParent(this);
	this.children.push(child);
}

/**
 * Sets the parent entry of this entry.
 * @param {Entry} parent The direct ancestor entry. 
 */
Entry.prototype.setParent = function(parent) {
	this.parent = parent;
}

/**
 * Indicates whether this entry is selectable.
 */
Entry.prototype.isSelectable = function() {
	return this.selectable;
}

/**
 * Indicates whether the given entry is visible or not.
 * 
 * @param {String} entryId The id of the entry to check.
 * @return true if visible, false otherwise.
 */
Entry.isVisibleEntry = function(entryId) {
	return entryId != "root" && !entryId.startsWith("group");
}

/**
 * Compares two entries, used for sorting algorithms.
 * @param {Entry} a
 * @param {Entry} b
 */
Entry.compareEntries = function(a, b) {
	if (a.label) a = a.label;
	if (b.label) b = b.label;
	
	a = a.toLowerCase();
	b = b.toLowerCase();
	
	try {
		if (a == b){
			return 0;	
		} else if (a > b) {
			return 1;
		} else {
			return -1;	
		}
	} catch (error) {
		// REVISIT Why does IE throw an error sometimes?
		// (because params are sometimes null, but why??)
		return 0;
	}
}


function TaxonomyComponent() {
	TaxonomyComponent.baseConstructor.call(this, null, "taxonomyComponent");
	
	// The root entry of the hierarchical hierarchy
	this.rootEntry = null;
	// List of all entries
	this.entriesList = [];
	// HashMap of all entries
	this.entriesHashMap = new HashMap();
	
	// raw XML string, needed by classification browser
	// NB Currently not used, due to problems in Internet Explorer (see BrowseByClassificationApp)
	this.xml = "";
	
	this.taxonomyLoadedEvent = new YAHOO.util.CustomEvent("taxonomyLoaded", this);
}
TaxonomyComponent.extend(Component);

TaxonomyComponent.TAXONOMY_URL = MACEConstants.rootURL + "components/taxonomy/php/vocabulary.xml.php";
//TaxonomyComponent.TAXONOMY_URL = MACEConstants.rootURL + "components/taxonomy/vocabulary.xml";
//TaxonomyComponent.TAXONOMY_URL = "../../components/taxonomy/vocabulary-part-s.xml";


/**
 * Returns login and logout as events this will broadcast.
 */
TaxonomyComponent.prototype.getBroadcastEvents = function() {
	console.log("TaxonomyComponent broadcasts: taxonomyLoaded");
	return [this.taxonomyLoadedEvent];
}

// Vocabulary handling methods ----------------------------

/**
 * Creates hierarchical vocabulary entries from Protegé's taxonomy XML. 
 * 
 * For each taxonomy item an Entry is created.
 */
TaxonomyComponent.prototype.loadTaxonomy = function(noParsing) {
	var _this = this;
	$.get(TaxonomyComponent.TAXONOMY_URL, {}, function(xml) {
		console.log("Loading taxonomy ...");
			
		if (!noParsing) {
			// parse into jQuery object, create lookup tables
			_this.parseXML(xml);
		} else {
			// raw XML string, needed by classification browser
			_this.xml = xml;
		}
		// Fires event and passes itself as argument
		_this.taxonomyLoadedEvent.fire(_this);
		
		// in order to retrieve raw text : type="text", else "xml"
	}, noParsing ? "text" : "xml");

}

/**
 * Creates a jQuery object from this.xml
 * 
 */
TaxonomyComponent.prototype.parseXML = function(xml) {
	console.log("Parsing taxonomy ...");
	// console.profile('Measuring time');
	var $rootItem = $(xml).find("item#root");
	this.rootEntry = this.createEntryWithChildren($rootItem);
	this.entriesList = this.getFlatEntryListFromAncestorEntry(this.rootEntry);
	// console.profileEnd();
	console.log("Taxonomy with " + this.entriesList.length + " entries parsed.");
}

/**
 * Creates entry tree from the XML.
 * Iterates recursively over all children XML items and adds them to the tree.
 * 
 * @param {jQuery} item The XML item to create an Entry with
 * @return {Entry} The created entry with its children
 */
TaxonomyComponent.prototype.createEntryWithChildren = function($item) {
	var entry = this.createEntryFromVocabularyItem($item);
	var _this = this;
	$item.children("children").children("item").each(function() {
		var $childItem = $(this);
		var child = _this.createEntryWithChildren($childItem);
		entry.addChild(child);
	});
	return entry;
}

/**
 * Gets a flat list with all descendant entries beneath the given entry.
 * Iterates recursively over all children entries and adds them to the list. 
 *
 * @param {Entry} entry The ancestor entry.
 * @param {boolean} addToList Indicates whether to add the given entry itself to the entry list.
 * @return {Array} The flat entry list.
 */
TaxonomyComponent.prototype.getFlatEntryListFromAncestorEntry = function(entry, addToList) {
	var entryList = [];
	// Do not add very first parent entry to the list (recursive calls set flag to true)
	if (addToList) {
		entryList.push(entry);
	}
	
	for (var i = 0; i < entry.children.length; i++) {
		var childEntry = entry.children[i];
		var childrenEntryList = this.getFlatEntryListFromAncestorEntry(childEntry, true);
		entryList = entryList.concat(childrenEntryList);
	}
	
	return entryList;	
}

/**
 * Creates a single Entry from given XML item.
 * 
 * @param {jQuery} $item The item to read.
 */
TaxonomyComponent.prototype.createEntryFromVocabularyItem = function($item) {
	// Reads all properties of an item
	var id = $item.attr("id");
	// any string evaluates to true, even "false", so we need to check for == "true"
	var selectable = $item.attr("selectable") == "true";
	
	// Create language-based hashmap with all synonyms
	var labels = new HashMap();
	$item.children("label").each(function (i) {
		var $label = $(this);
		var lang = $label.attr("lang");
		if (labels.get(lang) == null) {
			labels.add(lang, new Array());
		}
		labels.get(lang).push($label.text());
	});
	
	var entry = new Entry(id, labels, null, selectable, null);
	
	// REVISIT Add storing to map in Entry constructor?
	this.entriesHashMap.add(entry.id, entry);
	// REVISIT: (hopefully) temporary hack to enable faceted search lookup (ids are in lowercase)
	this.entriesHashMap.add(entry.id.toLowerCase(), entry);
	
	return entry;
}


// Search and find methods ----------------------------

/**
 * Finds an entry by given id (in given entries list).
 * 
 * @param {String} id The id of the entry to search for.
 * @return {Entry} Found entry or null if none found.
 */
TaxonomyComponent.prototype.findEntryById = function(id) {
	return this.entriesHashMap.get(id);
}

/**
 * Finds an entry by given label (in given entries list).
 * @param {List of Entry} entries
 * @param {String} label
 */
TaxonomyComponent.findEntryByLabel = function(entries, label) {
	if (!entries) return null;
	
	var i = 0;
	var found = false;
	
	var entry = null;
	
	while (i < entries.length && !found) {
		entry = entries[i];
		if (entry.label == label) {
			found = true;
		}
		i++;
	}
	
	return found ? entry : null;
}

/**
 * ClassificationWidget shows the classification terms of the LOM.
 * 
 * Events: If both taxonomy and LOM are loaded the assigned entries are loaded and displayed.
 * 
 * If user is logged in, editMode is on: Quick-Add with textbox is visible and remove-buttons appear.

 * TODO Fix add button: onClick handler only if not disabled.
 * TODO Fix add button: enter to submit form must work. 
 * TODO Fix hiding fx of facet (if removing last entry of a facet). Does work with entry li, but not with facet li.
 * REVISIT Refactor loading values. Instead of ContentEnrichmentService directly parse from lom.xml
 * TODO Use comment: First XML label is the main label, following ones (translations and synonyms) are used to improve the search.
 * 
 * 2008, 2009 by Till Nagel, nagel@fh-potsdam.de
 * 
 * @param {jQuery} $element
 */
function ClassificationWidget($element) {
	ClassificationWidget.baseConstructor.call(this, $element);
	
	// The TaxonomyComponent; reference set after taxonomyLoaded
	this.taxonomyComponent = null;

	// The currently selected entry. This one will be added.
	this.currentEntry = null;
	// The currenly selected parent entry. This is to show the breadcrumb navigation. 
	this.currentParentEntry = null;
	// The currently used entries for autocompletion.
	this.currentEntries = [];
	
	// The id of the content to classify
	this.contentId = null;
	// List of entries assigned to the content
	this.assignedEntries = [];

	// Inits view: displays, buttons, etc.
	this.$quickAdd = $("#quickAdd", $element);
	this.$quickAdd.hide();
	this.$inputField = $("#suggest", $element);
	this.$breadcrumbDisplay = $("#breadcrumbDisplay", $element);
	this.$breadcrumbDisplay.html(ClassificationWidget.EMPTY_BREADCRUMB);
	this.$breadcrumbResetButton = $("#resetBreadcrumb", $element);
	this.initBreadcrumbResetButton();

	this.$classificationAddButton = $("#classificationAddButton", $element);
	var _this = this;
	this.$classificationAddButton.click(function() {
		_this.addEntryToContent();
	});
	
	this.assignedEntriesDisplay = new AssignedEntriesDisplay($("#assignedEntries > .rowList", $element), this);

	this.messageBox = new MessageBox($("#messageBox", $element));
	
	console.log("ClassificationWidget created.");
}
ClassificationWidget.extend(Widget);

ClassificationWidget.GET_VALUES_URL = MACEConstants.widgetsURL + "classification2/php/getValues.php";
//ClassificationWidget.GET_VALUES_URL = "php/getValuesStatic-mock.php";
//ClassificationWidget.ADD_VALUES_URL = MACEConstants.rootURL + "php/mock.php?success=true&responseAfter=1&callback=?";
ClassificationWidget.ADD_VALUES_URL = MACEConstants.widgetsURL + "classification2/php/sendMetadata.php";
ClassificationWidget.REMOVE_VALUES_URL = MACEConstants.widgetsURL + "classification2/php/sendMetadata.php";

ClassificationWidget.EMPTY_BREADCRUMB = "<small>Start typing and pick a matching value</small>&nbsp;";


// Event handling -------------------------------

ClassificationWidget.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("login", this.onLogin), new SubscriptionEvent("logout", this.onLogout),
		new SubscriptionEvent("lomLoaded", this.onLomLoaded), new SubscriptionEvent("taxonomyLoaded", this.onTaxonomyLoaded)];
}

ClassificationWidget.prototype.onTaxonomyLoaded = function(type, args) {
	console.log("onTaxonomyLoaded");
	this.taxonomyComponent = args[0];
	this.initTextField(this.taxonomyComponent.entriesList);

	if (this.contentId != null) {
		this.loadAssignedEntries();
	}
}

ClassificationWidget.prototype.onLogin = function(type, args) {
	console.log("onLogin " + type + ", user=" + args[0] + ", this.id=" + this.id);
	
	this.$quickAdd.slideDown();
	// re-init list (to create remove-buttons) 
	this.assignedEntriesDisplay.allowValueRemoval = true;
	this.assignedEntriesDisplay.populate(this.assignedEntries);
}

ClassificationWidget.prototype.onLogout = function(type, args) {
	console.log("ClassificationWidget.onLogout " + type + ", user=" + args[0] + ", this.id=" + this.id);

	this.$quickAdd.slideUp();
	// re-init list (to remove remove-buttons) 
	this.assignedEntriesDisplay.allowValueRemoval = false;
	this.assignedEntriesDisplay.populate(this.assignedEntries);
}

ClassificationWidget.prototype.onLomLoaded = function(type, args) {
	var lom = args[0];
	console.log("ClassificationWidget.onLomLoaded: lom.id=" + lom.id + ", title=" + lom.title);
	this.contentId = lom.id;

	if (this.taxonomyComponent != null) {
		this.loadAssignedEntries();
	}
}

// ----------------------------------------------

/**
 * Creates breadcrumbReset button icon, and adds handler 
 */
ClassificationWidget.prototype.initBreadcrumbResetButton = function() {
	// Creates delete breadcrumb button
	this.$breadcrumbResetButton.html(' <a href="#"><img src="'+MACEConstants.rootURL+'img/icons/cross.png" /></a>');
	var _this = this;
	$("a", this.$breadcrumbResetButton).mouseover(function(){
		_this.$breadcrumbDisplay.addClass("todelete");
	});
	$("a", this.$breadcrumbResetButton).mouseout(function(){
		_this.$breadcrumbDisplay.removeClass("todelete");
	});	
	$("a", this.$breadcrumbResetButton).click(function(){
		_this.setCurrentEntry(null);
		return false;
	});
	this.$breadcrumbResetButton.hide();
}


// Autocompletion -----------------------------------------

/**
 * Initializes the auto-complete text field.
 * 
 * @param {Array} entries List of entries to be used for auto-completion.
 */
ClassificationWidget.prototype.initTextField = function(entries) {
	this.$inputField.unautocomplete();

	this.$inputField.autocomplete(entries, {
		matchContains: true,
		minChars: 0,
		max: 25,
		width: 300,
		// formatMatch: matching / searching
		formatMatch: function(data, i, n, value) {
			return data.getFullLabel() + (data.description ? " " + data.description : "");
		},
		// formatItem: show in list
		formatItem: function(data, i, n, value) {
			return data.getFullLabel() + (data.description ? "<br/><small>" + data.description + "</small>": "");
		},
		// formatResult: show in textbox
		formatResult: function(data, value) {
			return data.label;
		}
	});
	
	var _this = this;
	this.$inputField.result(function(event, data, formatted){
		console.log("User selected entry " + data.id);
		_this.setCurrentEntry(data);
	});
}

/**
 * Sets the current entry for storage, and descendents/siblings for further fine-grained autocompletion.
 * A linked breadcrumb from the entry's path is created. 
 * 
 * @param {Entry} entry The entry to use.
 */
ClassificationWidget.prototype.setCurrentEntry = function(entry) {
	this.currentEntry = entry;
	
	if (this.currentEntry == null) {
		// Autocomplete all entries
		console.log("No currentEntry, autocomplete all entries");

		// Inits autocompletion and resets all displays
		this.initTextField(this.taxonomyComponent.entriesList);
		this.$inputField.val("");
		this.$breadcrumbDisplay.html(ClassificationWidget.EMPTY_BREADCRUMB);
		this.$breadcrumbDisplay.removeClass("todelete");		
		this.$breadcrumbResetButton.hide();
		
		this.$classificationAddButton.attr("title", "");
		this.$classificationAddButton.addClass("disabled");
	}
	else {
		// Autocomplete only descendent entries
		console.log("An currentEntry, autocomplete only decendents or sibblings");
		
		// Selects autocompletion start entry; either itself if category, or its parent.
		// TODO Integrate entry.isSelectable()
		var newTextFieldValue = "";
		if (this.currentEntry.hasChildren()) {
			this.currentParentEntry = this.currentEntry;
		}
		else {
			this.currentParentEntry = this.currentEntry.parent;
			newTextFieldValue = this.currentEntry.label;
		}
		this.currentEntries = this.taxonomyComponent.getFlatEntryListFromAncestorEntry(this.currentParentEntry);
		console.log(this.currentEntries.length + " current entries in autocompletion.");
		
		// Inits autocompletion and shows value in text field
		this.initTextField(this.currentEntries);
		this.$inputField.val(newTextFieldValue);
		this.$inputField.focus(); // focus() needed so autocomplete key events work 
		
		// Create breadcrumb navigation
		this.createBreadcrumbNavigation();
		
		this.$classificationAddButton.attr("title", "Add '" + this.currentEntry.label + "' to this content");
		this.$classificationAddButton.removeClass("disabled");
	}
}

/**
 * Creates and displays the breadcrumb navigation based on current parent entry.
 * Additionally, delete button and its functionality.
 */
ClassificationWidget.prototype.createBreadcrumbNavigation = function() {
	var breadcrumb = this.createBreadcrumbHtml(this.currentParentEntry);
	this.$breadcrumbDisplay.html(breadcrumb);
	
	var _this = this;
	// Adds click handler to link of each entry in the path
	this.$breadcrumbDisplay.find("a").each(function(index){
		$(this).click(function(){
			_this.setCurrentEntryViaId($(this).attr("id"));
			return false;
		});
	});
	
	this.$breadcrumbResetButton.show();
}

/**
 * Convenient methods for @see #setCurrentEntry
 * @param {String} currentEntryId The id of the entry to set as current.
 */
ClassificationWidget.prototype.setCurrentEntryViaId = function(currentEntryId) {
	var entry = this.taxonomyComponent.findEntryById(currentEntryId);
	this.setCurrentEntry(entry);
}

/**
 * Creates a linked breadcrumb display from the entry and its hierarchy path.
 * @param {Entry} entry The leaf entry to start with.
 */
ClassificationWidget.prototype.createBreadcrumbHtml = function(entry) {
	var breadcrumb = "";
	if (entry.parent != null && Entry.isVisibleEntry(entry.parent.id)) {
		breadcrumb = this.createBreadcrumbHtml(entry.parent) + Entry.HIERARCHY_SEPARATOR;
	}
	breadcrumb += '<a href="#" id="' + entry.id + '">' + entry.label + '</a>';
	return breadcrumb;
}


// Update methods -------------------------------------------

/**
 * Adds the current entry to the content.
 * Displays it immediately as assigned entry, and tries to save the value.
 */
ClassificationWidget.prototype.addEntryToContent = function() {
	if (this.currentEntry == null) {
		console.warn("No entry to add selected");
		return;
	}

	console.info("Adding entry " + this.currentEntry + " to " + this.contentId + " ...");
	this.assignedEntriesDisplay.add(this.currentEntry, true);
	
	var json = ClassificationWidget.convertToJSON([this.currentEntry], null, this.contentId);
	console.log(json);
	
	var _this = this;
	$.getJSON(ClassificationWidget.ADD_VALUES_URL, {"contentId": this.contentId, "metadata": json}, function(result) {
		if (result.success) {
			console.log("Saved values successfully.");
			_this.assignedEntriesDisplay.onAddedSuccess(_this.currentEntry);
			_this.assignedEntries.push(_this.currentEntry);
			_this.setCurrentEntry(null);
		}
		else {
			console.warn("Error. Adding values failed: Could not update LOM.");
			_this.messageBox.showError("Adding failed.", true);
			// Reload assignedEntries
			_this.assignedEntriesDisplay.populate(_this.assignedEntries);
		}
	});
}

// TODO Implement removeEntryFromContent()
ClassificationWidget.prototype.removeEntryFromContent = function(entry) {
	console.info("Removing entry " + entry + " from " + this.contentId + " ...");
	
	var json = ClassificationWidget.convertToJSON(null, [entry], this.contentId);
	console.log(json);
	
	var _this = this;
	$.getJSON(ClassificationWidget.REMOVE_VALUES_URL, {"contentId": this.contentId, "metadata": json}, function(result) {
		if (result.success) {
			console.log("Removed values successfully.");
			_this.assignedEntriesDisplay.onRemovedSuccess(entry);
		}
		else {
			console.warn("Error. Removing values failed: Could not update LOM.");
			_this.messageBox.showError("Removing failed.", true);
			// Reload assignedEntries
			_this.assignedEntriesDisplay.populate(_this.assignedEntries);
		}
	});
	
}

/**
 * Returns given entries as JSON string.
 */
ClassificationWidget.convertToJSON = function(entriesToAdd, entriesToRemove, contentId) {
	var result = "{";
	result += "\"id\":\"" + contentId + "\",";
	if (entriesToAdd) {
		result += "\"entriesToAdd\":[" +
		$.map(entriesToAdd, function(entry){
			return entry.toJSON()
		}).toString() + 
		"]";
	}
	if (entriesToRemove) {
		result += "\"entriesToRemove\":[" +
		$.map(entriesToRemove, function(entry){
			return entry.toJSON()
		}).toString() +
		"]";
	}
	result += "}";
	
	// remove line breaks
	result.replace(/[\n\r]+/g, "");
	return result;
}


// Assigned entries ---------------------------------------

/**
 * Loads the assigned entries for the currently selected content.
 */
ClassificationWidget.prototype.loadAssignedEntries = function() {
	this.assignedEntries = [];
	
	var _this = this;
	$.getJSON(ClassificationWidget.GET_VALUES_URL, {"contentId": this.contentId}, function(result) {
		for (var i = 0; i < result.classification.length; i++) {
			var entryId = result.classification[i].id;
			var entry = _this.taxonomyComponent.findEntryById(entryId);
			if (entry != null) {
				_this.assignedEntries.push(entry);
			}
			else {
				console.warn("Entry " + entryId + " not found.");
			}
		}
		console.log("Loaded " + _this.assignedEntries.length + " assigned entries for content " + _this.contentId);
		_this.assignedEntriesDisplay.populate(_this.assignedEntries);
	});
}



/**
 * Displays the assigned entries. (View, only)
 * Can be populated with an array, but also a single entry can be added and removed.
 * 
 * Entries are shown grouped by their facets.
 * 
 * TODO Add normal and enlarged widget view (i.e. show full path for each entry)
 * REVISIT Use entryToBeRemoved-event instead of client reference
 */
function AssignedEntriesDisplay($assignedEntriesList, client) {
	this.$assignedEntriesList = $assignedEntriesList;
	this.clientWithRemoveHandler = client;
	this.allowValueRemoval = false;
}

AssignedEntriesDisplay.classificationBrowserURL = MACEConstants.rootURL + 'BrowseByClassification';


/**
 * Resets and populates the whole display list.
 *  
 * @param {Array} assignedEntries The entries to display
 */
AssignedEntriesDisplay.prototype.populate = function(assignedEntries){
	this.$assignedEntriesList.hide();
	this.$assignedEntriesList.empty();
	
	for (var i = 0; i < assignedEntries.length; i++) {
		var entry = assignedEntries[i];
		this.add(entry);
	}
	
	this.$assignedEntriesList.slideDown(330);
}

/**
 * Adds an entry to this display.
 * 
 * @param {Entry} entry The entry to add.
 * @param {boolean} highlight Indicates whether to show a visual hint for new entry. (optional, default: false)
 */
AssignedEntriesDisplay.prototype.add = function(entry, highlight) {
	console.log("AssignedEntriesDisplay: Adding " + entry);
	
	var $facetValuesList = this._getFacetValuesList(entry.getFacet());
	var html = this._createEntryHtml(entry);
	$facetValuesList.append(html);
	if (this.allowValueRemoval) {
		this.initRemoveButton(entry);
	}
	
	if (highlight) {
		// Displays list item in green and (after onAddedSuccess) fades to normal
		this._showWorking(entry, "new");
	}
}

AssignedEntriesDisplay.prototype.initRemoveButton = function (entry) {
	var $entryItem = $("#assignedEntry" + entry.id);
	var $entryIcon = $("#assignedEntry" + entry.id + " > .icon");
	$entryIcon.html('<a href="#" title="Remove term from content">Remove</a>');
	$entryIcon.show();
	jQuery.data($entryIcon.get(0), "entry", entry);
	
	var _this = this;
	$("a", $entryIcon).mouseover(function() {
		$entryItem.addClass("todelete");
	});
	$("a", $entryIcon).mouseout(function() {
		var el = $(this).parent().get(0);
		if (typeof jQuery.data(el, "todelete") == "undefined") {
			$entryItem.removeClass("todelete");
		}
	});	
	$("a", $entryIcon).click(function() {
		var el = $(this).parent().get(0);
		var entry = jQuery.data(el, "entry");
		jQuery.data(el, "todelete", true);
		_this.remove(entry);
		return false;
	});
}

/**
 * Removes an entry from this display.
 * 
 * @param {Entry} entry The entry to remove.
 */
AssignedEntriesDisplay.prototype.remove = function(entry) {
	console.log("AssignedEntriesDisplay: Removing " + entry);
	
	var $entryItem = $("#assignedEntry" + entry.id);
	if ($entryItem.length > 0) {
		this._showWorking(entry, "todelete");
	}
	
	this.clientWithRemoveHandler.removeEntryFromContent(entry);
}

/**
 * Handles event after entry was persistently added.  
 * @param {Object} entry The successfully added entry.
 */
AssignedEntriesDisplay.prototype.onAddedSuccess = function(entry) {
	console.log("AssignedEntriesDisplay.onAddeddSuccess");
	this._makeNewEntryPermanent(entry);
}

AssignedEntriesDisplay.prototype._makeNewEntryPermanent = function(entry) {
	var $entryItem = $("#assignedEntry" + entry.id);
	$entryItem.removeClass("working");
	$entryItem.removeClass("new", 1000);
}

/**
 * Handles event after entry was persistently removed.
 * @param {Entry} entry The successfully removed entry.  
 */
AssignedEntriesDisplay.prototype.onRemovedSuccess = function(entry){
	console.log("AssignedEntriesDisplay.onRemovedSuccess");
	this._hideAndRemoveEntry(entry);
}

/**
 * Removes entry or whole facet from display. Its parent facet is removed
 * if the entry was the last one.
 */
AssignedEntriesDisplay.prototype._hideAndRemoveEntry = function(entry) {
	var $entryItem = $("#assignedEntry" + entry.id);
	if ($entryItem.siblings().length > 0) {
		// Slide up entry itself, only
		$entryItem.slideUp(300);
	}
	else {
		console.log("hidAndRemoveEntry... whole facet");
		// Last entry, slide up whole facet
		var $facet = $entryItem.parent().parent();
		console.log($facet);
		$facet.slideUp(300);
	}
	
	// Real removing (after slide animation)
	$entryItem.oneTime("300ms", this._removeEntry);
}

AssignedEntriesDisplay.prototype._removeEntry = function() {
	if ($(this).siblings().length > 0) {
		// Remove entry itself, only
		$(this).remove();
	}
	else {
		// Last entry, remove whole facet
		var $facet = $(this).parent().parent(); 
		$facet.remove();
	}
}

AssignedEntriesDisplay.prototype._showWorking = function(entry, className) {	
	var $entryItem = $("#assignedEntry" + entry.id);
	$entryItem.addClass(className);
	$entryItem.addClass("working");
}


/**
 * Returns the facetValueList of the entry's facet. Creates it if not existed, yet.
 * @param {Object} facetEntry
 * 
 * // FIXME Implement boolean-param 'shallCreate'
 */
AssignedEntriesDisplay.prototype._getFacetValuesList = function(facetEntry) {
	var $facetValuesList = $("#assignedFacet" + facetEntry.id + " > .valueList");
	if ($facetValuesList.length == 0) {
		// Create new facet value list
		var html = this._createFacetListHtml(facetEntry);
		this.$assignedEntriesList.append(html);
		$facetValuesList = $("#assignedFacet" + facetEntry.id + " > .valueList");
	}
	return $facetValuesList;
}

AssignedEntriesDisplay.prototype._createFacetListHtml = function(facetEntry) {
	return '<li id="assignedFacet' + facetEntry.id + '"><div class="key">' + facetEntry.label + '</div><ul class="valueList"></ul></li>'; 
}

/**
 * Creates the HTML for an entry, with link and (potential) status indicator icon.
 */
AssignedEntriesDisplay.prototype._createEntryHtml = function(entry) {
	var labelLink = encodeURIComponent(entry.label);
	var html = '<li id="assignedEntry' + entry.id + '" class="value">';
	html += '<div class="label"><a href="' + AssignedEntriesDisplay.classificationBrowserURL + '#/?query=classification,,' + entry.id + ',' + labelLink + '" target="_parent" title="' + entry.getFullLabel() + '">' + entry.label + '</a></div>';
	html += '<div class="icon"></div></li>';
	return html;
}


/**
 * RelationWidget
 * 
 * Shows relations of the current LOM. These relations are not automatically calculated, but completely based
 * on the LOM relation-element.
 * 
 * 2009 by Till Nagel, nagel@fh-potsdam.de
 * 
 * @param {jQuery} $element
 */
function RelationWidget($element) {
	RelationWidget.baseConstructor.call(this, $element);
	
	this.$relationList = $("#relations ul", $element);
	
	this.relationDisplayTemplate = RelationWidget.defaultTemplate;
	
	this.lom = null;
	
	console.log("RelationWidget created.");
}
RelationWidget.extend(Widget);

RelationWidget.defaultLomURL = MACEConstants.detailsBaseSrc;

RelationWidget.defaultTemplate = "";
RelationWidget.defaultTemplate = ''
	+ '<li class="{%= kind %} span-6">'
		+ '{% if (url != null) { %}' 
		+ '<a href="{%= url %}" {% if (target != null) { %} target="{%= target %}" {% } %}>'
		+ '{% } else { %}'
		+ '<div class="noLink">'
		+ '{% } %}'
		
		+ '<h4>{%= kindLabel %}</h4>'
		+ '{%= label %}'
	
		+ '{% if (url != null) { %}'
		+ '</a>'
		+ '{% } else { %}'
		+ '</div>'
		+ '{% } %}'
	+ '</li>';


// Event handling -------------------------------

RelationWidget.prototype.getSubscriptionEvents = function() {
	return [new SubscriptionEvent("lomLoaded", this.onLomLoaded)];
}

RelationWidget.prototype.onLomLoaded = function(type, args) {
	this.$relationList.empty();
	this.lom = args[0];
	this.showRelations();
}

RelationWidget.prototype.showRelations = function() {
	var _this = this;
	var numRelations = this.lom.$xml.find("relation").size();
	if (numRelations > 0) {
		console.log("LOM has " + numRelations + " relations");
		var _this = this;
		this.lom.$xml.find("relation").each(function() {
			var relation = new LOMRelation($(this));
			_this.addRelationToList(relation);
		});
	}
	else {
		console.debug("LOM has NO relations");
		this.$element.hide();
	}
	
	if (this.lom.dbpediaID){
		var url = MACEConstants.rootURL + "tools/detailPage/php/getFlickrWrapprResults.php?id=" + this.lom.dbpediaID;
		
		console.log("Loading flickrwrapper results from " + url);
		var _this = this;
		var maxImages = (4-numRelations%4) + 4;
		$.get(url, {}, function(result){
			$(result).find("a").each(function(i){
				if (i < maxImages){
					var url = $(this).attr("href");
					var picURL = $(this).find("img").attr("src");
					_this.$relationList.append('<li class="flickrContent span-6"><a href="'+url+'"><div  style="height:100%; width:100%; background:center center url('+picURL+');"</div></a></li>');
				}		
			});
		}, "html");
	}
}

RelationWidget.prototype.addRelationToList = function(relation) {
	var htmlString = tmpl(this.relationDisplayTemplate, relation);
	this.$relationList.append(htmlString);
}


function LOMRelation($relation) {
	this.$relation = $relation;
	
	// Kind of relation
	this.kind = $relation.find("kind > value").text();
	this.kindLabel = LOMRelation.getLabelForKind(this.kind);
	
	this.setResourceIdAndCatalog();

	// Link to related resource
	this.setUrlAndTarget();
	
	// Label and description
	this.resourceDescription = null;
	var $desc = $relation.find("resource > description > string");
	if ($desc.length > 0) {
		this.resourceDescription = $desc.get(0).textContent;
	}
	if (this.resourceDescription != null && this.resourceDescription != '') {
		this.label = this.resourceDescription;
	}
	else {
		this.label = this.resourceId; 
	}	
}

LOMRelation.getLabelForKind = function(kind) {
	switch (kind.toLowerCase()) {
		case "isreferencedby":
			return "referenced by";
			break;
		case "ispartof":
			return "part of";
			break;			
		case "isrepresentedby":
			return "wikipedia source";
			break;						
		default:
			return kind;
	}
}

LOMRelation.prototype.setResourceIdAndCatalog = function() {
	if (this.$relation.find("resource > identifier").length > 1) {
		// TODO Decide whether to use first or second if multiple resource IDs (see ICONDA for examples)
		this.resourceCatalog = this.$relation.find("resource > identifier > catalog:first").text();
		this.resourceId = this.$relation.find("resource > identifier > entry:first").text();
	}
	else {
		this.resourceCatalog = this.$relation.find("resource > identifier > catalog").text();
		this.resourceId = this.$relation.find("resource > identifier > entry").text();
	}
}

LOMRelation.prototype.setUrlAndTarget = function() {
	// ISSN / ISBN -> http://www.worldcat.org/
	// dbPedia -> wikipedia
	// default (normal repo) -> Query LOM and get GUID and title
	// none -> display description

	if (this.resourceId != '') {
		console.log("RelationWidget: Relation does have identifier entry: " + this.resourceCatalog + ", " + this.resourceId);

		if (this.resourceCatalog.indexOf("ISSN") > -1) {
			this.url = "http://www.worldcat.org/issn/" + encodeURIComponent(this.resourceId);
			this.target = "_blank";
		}
		else if (this.resourceCatalog.indexOf("ISBN") > -1) {
			this.url = "http://www.worldcat.org/isbn/" + encodeURIComponent(this.resourceId);
			this.target = "_blank";
		}
		else if (this.resourceCatalog.indexOf("dbpedia") > -1) {
			this.url = this.resourceId.substring(8, this.resourceId.length).replace(/http:\/\/dbpedia.org\/resource\//, "http://en.wikipedia.org/wiki/");
			this.target = "_blank";
		}
		else {
			this.url = RelationWidget.defaultLomURL + encodeURIComponent(this.resourceId);
			this.target = null;
		}
	}
	else {
		console.log("RelationWidget: Relation does not have identifier entry");
	}
}

