
// Search Tool
// $Id: search.cfm 15868 2009-06-11 05:40:32Z raisch $

var SearchTool={

	name: 'st',

	Query: Class.create({

		initialize: function(id,target_id,service,count,start,sort_field,sort_criteria) {
			this._id=id;
			this._target_id=target_id||'leftcolumn';
			this._service=service||'/SearchApi/Query';
			this._count=count||25;
			this._start=start||0;
			this._sort_field=sort_field||'pubdate';
			this._sort_criteria=sort_criteria||'FieldDescending';

			this._elt=$(this._id);
			if(!this._elt) throw 'Query.new requires the id of an existing input element';

			this._target=$(this._target_id);
			if(!this._target) 
				throw 'Query.initialize requires the id of an existing target div into which '+
				      'it can load things';

			this.addObservers();

			this.terms    = new SearchTool.Terms(this);
			this.filters  = new SearchTool.AdvancedFilters(this);

			// grab the query str from a previous query
			if(document[SearchTool.name]) {
				// we do this because when we load the 
				// advanced filters form, we create a new 
				// query for it so it can, for example, have 
				// it's own independant terms popup.
				this._previous_query=document[SearchTool.name];
				this._elt.value=this._previous_query.value();
			}

			// and save ourself to the document as the current query
			document[SearchTool.name]=this;
			
			// note that document[SearchTool.name] always references
			// the most recently created query object which, if it's 
			// not the first query, will link to its most recent
			// preexisting query as this._previous_query

		},

		id:     function()    { return this._id; },
		elt:    function()    { return this._elt; },
		row:    function()    { 
			// the query's row is needed so we can add a terms popup
			// under the query input element.
			return this._elt.up(1);
		},
		
		value:  function(str) {
			// updates our elt.value as well as the elt value of any previous query
			if(!str) return this._elt.value;
			this._elt.setValue(str);
			if(this._previous_query) this._previous_query.value(str);
			return this;
		},
		
		// alex, note the snazzy use of the javascript comma operator :-)
		// Rob - pretty cool, dude 
		target:        function(elt) { return elt ? ( this._target=$(elt), this )     : this._target; },
		target_id:     function(id)  { return id  ? ( this._target_id=id, this )      : this._target_id; },
		count:         function(str) { return str ? ( this._count=str, this )         : this._count; },
		start:         function(str) { return typeof str !== 'undefined' ? ( this._start=str, this ) : this._start; },
		sort_field:    function(str) { return str ? ( this._sort_field=str, this )    : this._sort_field; },
		sort_criteria: function(str) { return str ? ( this._sort_criteria=str, this ) : this._sort_criteria; },

		uri:   function()    { // returns the uri used to submit the query to the webservice api
			var result='';
			var value=encodeURI(this._elt.value.strip());
			var filters=this.filters?this.filters.toString():'';
			
			// if value is empty, return
			if(value.length<=0) return;

			result=this._service+'?q='+value+
				(filters.length>0?'&aq='+encodeURI(filters):'')+
				'&nr='+this._count+
				'&fr='+this._start+
				'&sf='+this._sort_field+
				'&sc='+this._sort_criteria;
				
			return result;
		},
		
		focus: function() { // shortcut
			this._elt.focus();
			return this;
		},
		
		updateAll: function(val,refresh) {
			if(val) this._elt.value=val;
			$$('input.query').each(function(elt){elt.value=this._elt.value;}.bind(this));
			if(refresh) this.refresh();
			return this;
		},

		addObservers: function() { // add event handlers (observers)
			this._elt.observe('keyup', function(evt){ // observe keypresses
					var key=evt.keyCode;
					if(key > Event.KEY_INSERT)           this.updateAll().terms.load();
					else if(key === Event.KEY_BACKSPACE) this.updateAll().terms.load();
					else if(key === Event.KEY_DELETE)    this.updateAll().terms.load();
					else if(key === Event.KEY_RETURN)    this.filters.load(this,true);
					else if(key === Event.KEY_DOWN)      this.terms.nextTerm();
					else if(key === Event.KEY_UP)        this.terms.previousTerm();
					else                                 this.terms.hide();
			}.bindAsEventListener(this));

			this._elt.observe('blur',function(evt){
				function hideTerms() { this.terms.hide();  }
				hideTerms.bind(this).delay(0.2);
			}.bindAsEventListener(this));

			return this;
		},
		
		toggleSort: function(fieldname) {
			if(fieldname == this.sort_field())
				this.sort_criteria(this.sort_criteria() == 'FieldDescending' ? 'FieldAscending' : 'FieldDescending');
			else
				this.sort_field(fieldname).sort_criteria('FieldDescending');
			return this.refresh();
		},
		
		refresh: function() {
			if(this.terms)    this.terms.hide();
			if(!this.results) this.filters.load(true);
			else              this.results.load();
			return this;
		}

	}),

	Terms: Class.create({

		initialize: function(query,service,max) {
			if(!query) throw 'Terms.initialize requires a query';
			this._query=query;
			this._id=this._query.id()+'_terms';
			this._hilite='query_term_hilite';
			this._elt_id=this._id+'_table';
			this._service=service||'/SearchApi/Terms';
			this._max=max||20;

			var src='<table id="'+this._elt_id+'" class="query_terms">'+
				'<tbody>'+
					'<tr class="query_term">'+
						'<td class="query_term">&nbsp;</td>'+
					'</tr>'+
				'</tbody>'+
			'</table>';

			this._query.row().down('input[type="text"]').insert({'after':src});

			$(this._elt_id).absolutize();
			this._elt=$(this._elt_id);
			this.hide();
		},

		uri: function() {
			return '#{service}?p=#{pattern}&num=#{max}'.interpolate({
				service:this._service,
				pattern:this._query.value(),
				max:this._max
			});
		},

		addTerms: function(term) {
			// if called with multiple arguments, reinvoke with each one
			if(arguments.length > 1) {
				$A(arguments).each(function(term){
					this.addTerms(term);
				}.bind(this));
				return this;
			}
			// if called with an array, reinvoke with each element
			if(typeof arguments[0] != 'string') {
				$A(arguments[0]).each(function(term){
					this.addTerms(term);
				}.bind(this));
				return this;
			}

			var term=term.strip();

			var count=this._elt.down().childElements().length + 1;

			var rid='#{id}_term_row_#{count}'.interpolate({id:this._id,count:count});
			var tid='#{id}_term_#{count}'.interpolate({id:this._id,count:count});

			var row='<tr id="#{rid}" class="#{class}"><td id="#{tid}" class="#{class}">#{term}</td></tr>'
				.interpolate({'class':'query_term',tid:tid,term:term});

			this._elt.down().insert({'bottom': row});

			var col=$(tid);

			col.observe('click',function(evt){
				var col=Event.element(evt);
				this._query.value(col.innerHTML).refresh();
				this._query.updateAll();
			}.bindAsEventListener(this));

			col.observe('mouseover',function(evt){
				var col=Event.element(evt);
				this._elt.down().childElements().invoke('removeClassName',this._hilite);
				col.addClassName(this._hilite);
			}.bindAsEventListener(this));

			col.observe('mouseout',function(evt){
				var col=Event.element(evt);
				col.removeClassName(this._hilite);
			}.bindAsEventListener(this));

			col.observe('keyup',function(evt) {
				var row=Event.element(evt);
				var key=evt.keyCode;
				if(key === Event.KEY_RETURN)     this._query.value(row.innerHTML).refresh();
				else if(key === Event.KEY_DOWN)  this.nextTerm();
				else if(key === Event.KEY_UP)    this.previousTerm();
			}.bindAsEventListener(this));

			return this;
		},

		load: function() {
			var value=this._query.value()||'';

			this.clear().hide();

			if(value.length <= 0)               return;  // no value
			if(value.match(/^\d+$/))            return;  // value is numeric
			if(value.indexOf('@') > 0)          return;  // value contains field ref

			new Ajax.Request(this.uri(),{
				method:'get',
				timeout:3,
				onSuccess:function(transport) {
					var src=transport.responseText.strip();
					if(src.length > 0) this.addTerms(src.split(/\r?\n/)).show();
				}.bind(this)
			});

			return this;
		},

		unload: function() {
			this.hide();
			this._elt.down().childElements().each(function(elt){
				elt.down().stopObserving();
				elt.remove();
			});
			this.clear();
			this._elt.remove();
			return null;
		},

		clear: function() {
			this._elt.down().childElements().invoke('remove');
			return this;
		},
		
		show:  function() {
			this._elt.show();
			return this;
		},
		
		hide:  function() {
			this._elt.hide();
			return this;
		},

		checkRow: function(dir,row) {
			var sibling;
			if(row.down().hasClassName(this._hilite)) {
				row.down().removeClassName(this._hilite);
				try { sibling=eval('row.'+dir+'()'); }
				catch(e) { /* purposefully ignored */ }
				if(sibling) {
					sibling.down().addClassName(this._hilite);
					this._query.value(sibling.down().innerHTML).focus();
					return true;
				}
			}
			return false;
		},

		nextTerm: function(hilite) {
			if(hilite) this._hilite=hilite;
			var rows=this._elt.down().childElements();
			if(!rows.find(this.checkRow.bind(this,'next')))
				if(rows && rows.first())
					this._query.value(rows.first().down().addClassName(this._hilite).innerHTML).focus();
			this.show();
			this._query.updateAll();
		},

		previousTerm: function(hilite) {
			if(hilite) this._hilite=hilite;
			var rows=this._elt.down().childElements();
			if(!rows.find(this.checkRow.bind(this,'previous')))
				if(rows && rows.last())
					this._query.value(rows.last().down().addClassName(this._hilite).innerHTML).focus();
			this.show();
			this._query.updateAll();
		}
	}),

	AdvancedFilters: Class.create({

		initialize: function(query,uri,link_id,form_id,advanced_id) {
			this.query=query;
			this.id='query_filters';
			this.elt=$(this.id);
			this.link_id=link_id||this.id+'_link';
			this.form_id=form_id||this.id+'_form';
			this.advanced_id=advanced_id||this.id+'_advanced_link';
			this.query_id='filters_query';
			this.uri=uri||'/_layout/inc/advanced_search.cfm';
			this.broken='<div>Unable to load advanced query filters.  Please try again later.</div>';
			this.filters={};
		},

		load: function(autoloadResults) {

			// handle refcodes			
			if($F('Printrefcode') && this.query.value()) {
				document.location='/index.cfm?fuseaction=Article.PRCSearch&query='+this.query.value();
				return;
			}

			if($(this.id)) {
				if(autoloadResults) {
					this.results=new SearchTool.Results(this);
					this.results.load();
				}
			}
			else {
				new Ajax.Request(this.uri,{
					method:'post',
					timeout:60,
					onSuccess:function(autoloadResults,transport) {
						var src=transport.responseText||this.broken;
						this.query.target().update(src);
						this.elt=$(this.id);
						
						if($(this.query_id)) {
							if($(this.advanced_id))	$(this.advanced_id).stopObserving();
							this.query=new SearchTool.Query(this.query_id);
						}
						
						this.results=new SearchTool.Results(this);
						
						$(this.link_id).observe('click',function(evt){
							this.results.load();
						}.bind(this));
						
						if(autoloadResults) this.results.load();
						
					}.bind(this,autoloadResults),
					onFailure:function() {
						this.elt.update(this.broken);
					}.bind(this)
				});
			}
			return false;
		},

		set: function(name,value,op) {
			var op=op||'=';
			if(value) this.filters[name]={'op':op,'val':value};
			else delete this.filters[name];
			return this;
		},

		get: function(name) {
			return this.filters[name];
		},
		
		fromSource:function(elt) {
			elt=$(elt);
			if(!elt) return;
			if(elt.value) this.set('@syssource',elt.value,'==');
			else this.set('@syssource');
			return this;
		},

		withinDays:function(elt) {
			elt=$(elt);
			if(!elt) return;
			if(elt.value) this.set('@pubdate','today-'+elt.value+'d','>=');
			else this.set('@pubdate');
			return this;
		},

		resultsPerPage:function(elt) {
			elt=$(elt);
			if(!elt) return;
			if(elt.value && elt.value > 0) {
				this.query.count(elt.value);
				this.query.start(0);
			}
			return this;
		},

		sortBy:function(field) {
			if(field) {
				var q=this.query;
				var qf=q.sort_field();
				var qc=q.sort_criteria();
				if(qf!==field) q.sort_field(field).sort_criteria('FieldAscending');
				else q.sort_criteria(qc==='FieldAscending'?'FieldDescending':'FieldAscending');
			}
			return this;
		},

		toString: function() {
			var result='';
			for(var name in this.filters) {
				var filter=this.filters[name];
				if(result.length > 0) result+=' ';
				result+=name+filter.op+filter.val;
			}
			return result;
		}
	}),

	Results: Class.create({

		initialize: function(filters) {
			this.filters=$(filters);
			if(!this.filters) throw 'Results.initialize requires a filters object';
			this.query=this.filters.query;
			if(this.query.terms) this.query.terms.hide();
			this.filters.results=this.query.results=this;
			
			this.id_base     = 'query_result';
			this.list_id     = this.id_base+'_list';
			this.busy_id     = this.id_base+'_busy';
			this.document_id = this.id_base+'_document';
			this.return_id   = this.id_base+'_return';
			
			this.busy_img_link = '/_layout/img/loading_wh.gif';
			
			this.document_service='/Indexable';
			
			this.should_warn = false; // should we warn when attempting to navigate away from results?
			
			this.broken='<div>Unable to load results.  Please try again later.</div>';
			this.hide();
		},
		
		busy: function() {
			var elt=$(this.busy_id);
			this.hide();
			var img='<img src="'+this.busy_img_link+'"/>';
			if(!elt.innerHTML.toLowerCase().match('<img')) {
				elt.update(img);
			}
			elt.show().setStyle({top:0,left:0,width:'100%',height:'500px'});
			return this;
		},
		
		list: function(src) {
			var elt=$(this.list_id);
			this.removeWarning();
			this.hide();
			if(src) elt.update(src);
			elt.show().setStyle({top:0,left:0,width:'100%',height:'100%'});
			return this;
		},
		
		document: function(src) {
			var elt=$(this.document_id);
			this.hide();
			elt.update('');
			$(this.return_id).show();
			if(src)	elt.update(src);
			//this.addWarning();
			elt.show().setStyle({top:0,left:0,width:'100%',height:'100%'});
			return this;
		},
		
		hide: function() { // shorthand
			this.query.focus();
			[
				$(this.list_id),
				$(this.busy_id),
				$(this.document_id),
				$(this.return_id)
			].invoke('hide');
			document.stopObserving('beforeUnload', this.warn);
			return this;
		},
		
		firstPage:    function(page,pages) { this.load(1);      },
		previousPage: function(page,pages) { this.load(page-1); },
		nextPage:     function(page,pages) { this.load(page+1); },
		lastPage:     function(page,pages) { this.load(pages);  },
		gotoPage:     function(page)       { this.load(page);   },
		
		load: function(page) {
			if(!this.query) throw 'Results.load requires a query';
			
			this.query.start((page-1)*this.query.count()||0);
			var uri=this.query.uri();
			if(!uri) return;
			
			this.busy();
			new Ajax.Request(uri,{
				method:'post',
				timeout:60,
				onSuccess:function(transport){
					this.list(transport.responseText||this.broken);
				}.bind(this),
				onFailure:function(){
					this.list(this.broken).ready();
				}.bind(this)
			});
		},
		
		display: function(id) { // called from result list with article link id
			elt=$(id);
			if(!elt) return;
			var link=elt.getAttribute('link');
			var href=elt.getAttribute('href');
			var uri=(link||href) + '?plain=1';
			this.busy();
			new Ajax.Request(uri,{
				method:'get',  // must be get!!!
				timeout:60,
				onSuccess:function(uri,transport){
					this.document(transport.responseText||'');
				}.bind(this,uri)
			});
		},
		
		unload_warning: function() {
			return 	'If you leave this page, your search results will be lost and you will need '+
							'to run your query again. To return to the search results, use the "<< Back to Search Results" link';
		},
		
		addWarning: function()    { window.onbeforeunload=null; window.onbeforeunload=this.unload_warning; return this; },
		
		removeWarning: function() { window.onbeforeunload=null; return this; }
		
	})
	
};

// set up the system...
document.observe('dom:loaded',function(){

	// set the name of our searchtool query object
	SearchTool.name='st'; // this is redundant as 'st' is the internal default
	
	// create a new SearchTool for the element id 'query'
	// which also assigns itself to document[SearchTool.name] (document.st)
	// note the assignment here is also redundant
	document[SearchTool.name]=new SearchTool.Query('query');
	
}.bindAsEventListener(document));
