/* 

Sites where this is used:
	podcast.ucsd.edu
	software.ucsd.edu * TODO: Update *
	*add your sites here, and update all copies, even if you only change the list*

Script: LiveSearch.js
	Contains LiveSearcher class to filter elements

License:
	Copyright 2008, Regents of the University of California

Notes:
	Requires Mootools version 1.2 with at least the following components:
		Core:
			Core
		Class:
			Class
			Class.Extras
		Native:	
			Array
			Element
			Function
			String
		Element:
			Element.Event
			Element.Selectors
			Element.Form
		Window:
			Window.Domready


	Developed by Matthew Fedder of the Instructional Web Development Center,
	Academic Computing Services, UC San Diego
	matthewf@ucsd.edu

Class: LiveSearcher
	Creates a search field that will interactively apply specified classes to any elements in 
	a list of elements that match any terms in that search field. Most commonly, this will be
	used to apply a class with a property 'display: hidden' to items that do not match any search
	terms.

Implements:
	<Events>, <Options>

Syntax:
	>var searcher = new LiveSearcher(el, els[, options]);

Arguments:
	searcherTarget - (element or id) Element into which the search field will be injected
	searchElements - (array of elements or selector) Elements which will be shown/hidden by this searcher
	options - (object, optional) see Options below

Options:
	defaultSearchText:    Text that will be displayed in the search box when it's left empty
	labelText: Text to use as a label for the search box

	hiddenItemClassname:   Name of CSS class applied to hidden Items
	visibleItemClassname:   Name of CSS class applied to hidden Items

	zebraStripeOddClassname: Name of CSS class applied to first and alternate items thereafter 
	zebraStripeEvenClassname: Name of CSS class applied to second and alternate items thereafter

	progessImageURL:      URL to image that will display while we're searching for items
	searchDelay:          Numeric value of time in milliseconds to wait between a keypress and searching

Example:
	new LiveSearcher('searchbox_div', $$('#searchin table table tr'), {
		defaultSearchText: 'Search Terms', 
		progressImageURL: 'img/progress.gif',

		visibleItemClassname: 'highlightRow', 
		hiddenItemClassname: 'hiddenRow', 

		zebraStripeEvenClassname: 'even',
		zebraStripeOddClassname: 'odd',
		});
*/
var LiveSearcher = new Class({
	Implements: [Options],
	options: {
		defaultSearchText: 'Search',
		labelText: 'Search: ',

		hiddenItemClassname: '',
		visibleItemClassname: '',
		
		zebraStripeEvenClassname: '',
		zebraStripeOddClassname: '',

		progressImageURL: false,
		searchDelay: 200, 

		searchFoundExtraNote: ''
		},

	searcherTarget: null, 
	searchElements: null,

	searchNote: null, // a div where a count of items found, or a progress imgage, will be placed
	delayedCall: null, 
	searchInputBox: null, 

	initialize: function(searcherTarget, searchElements, options)
		{
		this.setOptions(options); 

		// Find the target for the search box
		this.searcherTarget = $(searcherTarget); 

		// Get a list of items to search
		this.searchElements = $$(searchElements); 

		// Set up the location to inject the search input box
		if(this.searcherTarget == null)
			{
			this.searcherTarget = new Element('div', {
				'id': 'LiveSearcher_SearchInputBoxContainer'
				}).injectBefore($$('p', 'table')[0]); 
			}

		// Set up the label
		this.searchInputBoxLabel = new Element('label', {
			'for': 'LiveSearchInputBox'
			}).set('text', this.options.labelText).injectInside(this.searcherTarget); 

		// Set up the search input box
		this.searchInputBox = new Element('input', {
			'id': 'LiveSearchInputBox',
			'value': this.options.defaultSearchText,
			'emptyIf': this.options.defaultSearchText,
			'searcher': null, 
			'events': {
				// Add an action to clear the search box when it's clicked on
				'focus': function() { 
					if(this.value == this.searcher.options.defaultSearchText) this.value = ''; },
				// .. and likewise to restore the default text if it's left empty
				'blur': function() { 
					if(this.value == "") this.value = this.searcher.options.defaultSearchText; },

				// When a key is pressed, set in motion the search updater. 
				'keyup': function(e) {
					var event = new Event(e); 
					if(event.key.length == 1 && event.key.match(/[a-zA-Z0-9]/) || 
						event.key == 'backspace')
						{
						this.searcher.setDelayedCall(); 
						return true;
						}
					window.fireEvent(e); 

					return false;
					}
				}
			}).injectInside(this.searcherTarget); 

		this.searchInputBox.searcher = this; 

		// Set up and inject the box where notes or a progress indicator can go
		this.searchNote = new Element('span', {
			'styles': {
				'margin-left': '10px'
				}} ).injectInside(this.searcherTarget); 

		if(document.location.search.length > 0)
			{
			var queries = document.location.search.substring(1).split("&"); 
			var search = ""; 
			queries.each(function (query) {
				var parts = query.split("="); 
				if(parts[0] == "q")
					search = parts[1].replace("%20"," ").replace("+", " "); 
				}); 
			if (search != "")
				this.searchInputBox.value = search;

			this.updateSearch(); 	
			}
			

		}, // end initialize
	
	// If a progress image was set, display it
	activateProgressURL: function() {
		if(this.options.progressImageURL != false && 
			!this.searchNote.innerHTML.contains(this.options.progressImageURL))
			{
			this.searchNote.empty(); 
			new Element('img', {src: this.options.progressImageURL}).injectInside(this.searchNote); 
			}
		},
	
	// Set a timer to update the search a period of time after a key is pressed
	setDelayedCall: function() {
		try { $clear(this.delayedCall); } catch (Exception) { /*console.log(Exception); */ } 
		this.activateProgressURL(); 

		this.delayedCall = this.updateSearch.bind(this).delay(this.options.searchDelay); 
		},

	// Go through the list of searchElements and show only those items matching every item in the search list
	updateSearch: function() {
		this.activateProgressURL(); 

		// Set up an array of the clauses in the search string
		var clauses = (this.options.defaultSearchText == this.searchInputBox.value)?new Array():this.searchInputBox.value.toLowerCase().trim().split(" "); 
		var numClauses = clauses.length; 
		if(numClauses == 1 && clauses[0] == "")
			{
			numClauses = 0; 
			clauses = new Array(); 
			}


		var numFound = 0; // Number of search elements that match all search clauses
		var currentClass = this.options.zebraStripeOddClassname; // The current zebra stripe class to add

		// Iterate over the search elements, deciding if each should be shown
		this.searchElements.each(function(item)
			{
			// By default, show any particular item
			var displayItem = true; 

			// Check the search terms
			var data = item.innerHTML.toLowerCase(); 

			for(var i = 0; i < numClauses; i++)
				{
				if(!data.contains(clauses[i]))
					{
					displayItem = false; 

					// Skip the rest of the clauses
					i = numClauses; 
					}
				}


			if(displayItem)
				{
				// IF the item should be be shown, set it to its default display style
				item.removeClass(this.options.hiddenItemClassname); 
				item.addClass(this.options.visibleItemClassname); 

				numFound++; 
				}
			else
				{
				// Hide the item
				item.removeClass(this.options.visibleItemClassname); 
				item.addClass(this.options.hiddenItemClassname); 
				}

			// Reset the zebra stripes
			item.removeClass(this.options.zebraStripeOddClassname); 
			item.removeClass(this.options.zebraStripeEvenClassname); 

			// add the correct stripe class
			item.addClass(currentClass); 


			if(item.getStyle('display') != 'none' && item.getStyle('visibile') != 'hidden')
				{
				// Toggle the zebra striping between odd and even
				if(currentClass == this.options.zebraStripeOddClassname)
					currentClass = this.options.zebraStripeEvenClassname; 	
				else
					currentClass = this.options.zebraStripeOddClassname; 	
				}
			}.bind(this)); 

		if(numFound == 0)
			{
			this.searchNote.set('text', 'No Matches Found'); 
			} 
		else
			{
			if(numClauses > 0)
				{
				var s = numFound == 1?'':'es';
				this.searchNote.set('text', numFound + ' Match' + s + ' Found' + this.options.searchFoundExtraNote); 
				
				var newsearch = "?q=" + this.searchInputBox.value.replace(" ","%20"); 
				var link = document.location.protocol  + "//" + document.location.host + document.location.port + document.location.pathname + newsearch + document.location.hash; 
				if(link != document.location)
					{
					new Element("a", {
						"href": link,
						"events": {
							'click': function(e) {
								e = new Event(e);

								var input = new Element("input", {
									"id": "livesearch_href_input",
									"name": "livesearch_href_input",
									"value": this.href, 
									"styles": {
										"width": "300px"
										}
									});
									
								var label = new Element("label", {
									"for": "livesearch_href_input"
									}); 
								label.set('text', "Link: "); 

								var closer = new Element("img", {
									"src": "http://software.ucsd.edu/img/closer.png",
									"events": {
										"click": function() {
											this.getParent().dispose(); 
											}
										},
									"styles": {
										"position": "absolute",
										"top": "5px",
										"right": "5px",
										"border": "1px solid #7DA8D3"
										},
									"tabindex": "-1",
									"alt": "Close Link Box"
									}); 


								var box = new Element("div", {
									'styles': {
										"position": "absolute",
										"top": this.offsetTop + this.offsetHeight + 2,
										"left": this.offsetLeft,
										"width": "400px",
										"background": "#DAE4EE",
										"border": "1px solid #7DA8D3",
										"padding": "10px"
										}
									});
								closer.injectInside(box); 
								label.injectInside(box); 
								input.injectInside(box); 
								
								box.injectAfter(this); 
								input.focus(); 
								input.select();
								e.stop();
								}
							},
						"styles":
							{
							"position": "relative"
							}

						}).set('text', "link")
						.injectInside(this.searchNote.appendText(" - ")); 
					}
				}
			else
				this.searchNote.set('text', ""); 
			}
		}
	});  // end LiveSearcher class definition

LiveSearcher.implement(new Events, new Options); 


