1 /**
  2  * @fileOverview  This file contains model for building searchclausesets used in user queries.
  3  * Consists of two parts:
  4  * - SearchClause model [index,relation, value], with basic functionality - 
  5  * creation, render, autocomplete.
  6  * - Searchclauseset - container of actually used searchclauses in app
  7  * with basic functionality for building container - add, remove, clear
  8  * and conversion functions - conversions from-to querystring
  9  *
 10  * @author 
 11  * @version 
 12  */
 13 
 14 // client-side modelling of "business-objects":
 15 // SearchClause, SearchClauseset, 
 16 
 17  
 18  //TODO use select-options or autocomplete ???
 19  //var  relation_autocomplete = new Array('any','all','=','>','<'); 
 20  //var  value_autocomplete = new Array();
 21 
 22 /**
 23  * SearchClause model [index,relation, value], with basic functionality - 
 24  * creation, render, autocomplete.
 25  * @constructor
 26 */ 
 27 function SearchClause(index, relation, value ) {
 28 	this.index = index;
 29 	this.relation = relation;	
 30 	this.value = value;
 31 	this.negation  = false;
 32 	
 33 	this.is_category = false;
 34 	this.category = "";
 35 	
 36 	this.i = 0;
 37 	this.j = 0;
 38 	this.listid = "";
 39 	this.container = {};
 40 	
 41 	if (this.relation == "") {
 42 		this.relation = "=";
 43 	}
 44 }
 45 
 46 SearchClause.prototype.GetIndexInput = function(){
 47 	return $($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.index-input');
 48 };
 49 
 50 SearchClause.prototype.GetRelInput = function(){
 51 	return $($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.rel_input');
 52 };
 53 SearchClause.prototype.GetValueInput = function(){
 54 	return $($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.value-input');
 55 };
 56 
 57 SearchClause.prototype.GetIndexSelect = function(){
 58 	return $($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.index_select');
 59 };
 60 
 61 
 62 SearchClause.prototype.GetValueSelect = function(){
 63 	return $($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.value_select');
 64 };
 65 
 66 SearchClause.prototype.render = function (rel) {
 67 	
 68 	//TODO defaults
 69 	//this.relation = '=';
 70 	//var index_detail = "";
 71 	//if ((this.i == 0) && (this.j == 0)) {
 72 	//	index_detail = "<span name='detail_index' class='cmd cmd_detail'></span>";
 73 	//}
 74 	var x = "<div id='" + this.j + "' class='sc-j or_level' ><div id='" + this.listid + "' class='sc-wrapper focused' > " +
 75 	"<div class='search'><span class='index-search'><select class='index_select'/><input type='text' class='index-input autocomplete-input' /></span>" + 
 76 	//index_detail +
 77 	//"<select type='text' class='rel_input' />" +
 78 	"<select class='rel_input'><option value='='>=</option><option value='>'>></option><option value='<'><</option><option value='any'>any</option><option value='contains'>contains</option><option value='all'>all</option></select>" + 
 79 	"<select class='value_select'/><input type='text' class='value-input' /><span name='detail_value' class='cmd cmd_detail'></span>" +
 80 	 "<span class='sc_cmds'><span class='cmd cmd_sc_delete'> </span><span class='sc_id'>" + this.listid + "</span>" + 
 81 	  "<span class='cmd cmd_add_and'> </span><span class='cmd cmd_add_or'></span>"+
 82 	" </span></div><div class='index-context'><table></table></div></div></div>";
 83 	
 84 	addToSClist(x, this.i, this.j, rel);	
 85 	
 86 	
 87 	var e_index = this.GetIndexInput();
 88 	//new $.GrowingInput(e_index, {max: 80});
 89 	var e_relation = this.GetRelInput();
 90 	var e_value = this.GetValueInput();
 91 	var index_select = this.GetIndexSelect();
 92 	var value_select = this.GetValueSelect();
 93 	
 94 	$(index_select).hide();
 95 	value_select.hide();
 96 	//$(e_index).closest('.sc-wrapper').find('.index-context').hide();
 97 	
 98 	//alert("focused: "  + $('.focused','#searchclauselist').size());
 99 	$('.sc-wrapper','#searchclauselist').each(function(){
100 		$(this).removeClass("focused");
101 	});
102 	$(e_index).closest('.sc-wrapper').addClass("focused");
103 	//alert("focused: "  + $('.focused','#searchclauselist').size());
104 	
105 	// set focus
106 	e_index.focusin(function(){
107 		$('.sc-wrapper','#searchclauselist').each(function(){
108 			$(this).removeClass("focused");
109 		});
110 		$(this).closest('.sc-wrapper').addClass("focused");
111 	});
112 	index_select.focusin(function(){
113 		$('.sc-wrapper','#searchclauselist').each(function(){
114 			$(this).removeClass("focused");
115 		});
116 		$(this).closest('.sc-wrapper').addClass("focused");
117 	});
118 	e_relation.focusin(function(){
119 		$('.sc-wrapper','#searchclauselist').each(function(){
120 			$(this).removeClass("focused");
121 		});
122 		$(this).closest('.sc-wrapper').addClass("focused");
123 	});
124 	e_value.focusin(function(){
125 		$('.sc-wrapper','#searchclauselist').each(function(){
126 			$(this).removeClass("focused");
127 		});
128 		$(this).closest('.sc-wrapper').addClass("focused");
129 	});
130 	$($($('.sc-i','#searchclauselist')[this.i]).find('.sc-j')[this.j]).find('.cmd_detail').click(function(){
131 		$('.sc-wrapper','#searchclauselist').each(function(){
132 			$(this).removeClass("focused");
133 		});
134 		$(this).closest('.sc-wrapper').addClass("focused");
135 	});
136 	
137 	//TODO handle keydown
138 /*	e_index.keydown(function(ev){
139 		var evStop = function(){ ev.stopPropagation(); ev.preventDefault(); };
140 		if (ev.which === 13) evStop();
141 		var evFocusNext = function(){
142 			ev.preventDefault();
143 			e_relation.focus();	
144 		};
145 		//notifyUser($(this).attr("value").length);
146 		if ($(this).attr("value").length > 0 &&  ev.which === 32) evFocusNext(); // focus next element
147 		
148 	});
149 	
150 	e_relation.keydown(function(ev){
151 		var evStop = function(){ ev.stopPropagation(); ev.preventDefault(); };
152 		if (ev.which === 13) evStop();
153 		var evFocusNext = function(){
154 			e_value.focus();	
155 		};
156 		if (ev.which === 32) evFocusNext(); // focus next element
157 		notifyUser(ev.which);
158 	});
159 */
160 	
161 	//selects for the first use
162 	index_select.change(function(){
163 		$(this).next().val($(this).find('option:selected').text());
164 		$(this).hide();
165 		$(this).next().show();
166 	});
167 	value_select.change(function(){
168 		$(this).next().val($(this).find('option:selected').text());
169 		$(this).hide();
170 		$(this).next().show();
171 	});
172 	
173 	e_index.val(this.index);
174 	e_relation.val(this.relation);
175 	e_value.val(this.value);
176 	//update after text change
177 	e_index.bind('change', function(){
178 		
179 		var i = $(this).closest('.sc-i').attr("id");
180 		var j = $(this).closest('.sc-j').attr("id");
181 		//alert("e-index-chabge;" + i + j + $(this).val());
182 		searchclauseset.searchclauses[i][j].index = $(this).val();
183 		notifyUser("index_sc" + i + "-" + j + " changed to:" + searchclauseset.searchclauses[i][j].index, "debug");
184 		searchclauseset.searchclauses[i][j].is_category = false;
185     });
186 	e_relation.change(function(){
187 		var i = $(this).closest('.sc-i').attr("id");
188 		var j = $(this).closest('.sc-j').attr("id");
189 		//notifyUser("rel change:" + $(this).val(), 'debug');
190 		searchclauseset.searchclauses[i][j].relation = $(this).val();
191 		
192 		searchclauseset.searchclauses[i][j].is_category = false;
193 	});
194 	e_value.change(function(){
195 		var i = $(this).closest('.sc-i').attr("id");
196 		var j = $(this).closest('.sc-j').attr("id");
197 		searchclauseset.searchclauses[i][j].value = $(this).val();
198 		
199 		searchclauseset.searchclauses[i][j].is_category = false;
200 	});
201 	
202 	try{
203 		this.initAutocomplete();
204 	} catch (e){
205 		notifyUser("autocomplete failed: " + e.message);
206 	}
207 
208 
209 	/*
210 	 e_value.autocompleteArray(value_autocomplete,{
211 				autoFill:true,
212 				width:90
213 	});
214 	 */
215 	
216 	
217  };
218  
219  SearchClause.prototype.initAutocomplete = function(){
220 	
221 	 if (element_autocomplete.length == 0) return;
222 	  
223 	 //autocomplete
224 		function handleTermsSelection(i,j,elem){
225 			
226 			var context = $('#' + 'sc' +  i + '-' + j).find('.index-context');
227 			// fill context
228 			//$(context).remove();
229 			$(context).html(elements_hashtable[elem]);
230 			
231 			/*
232 			$(context).children('table').children().remove();
233 			for(var ii=0;ii< elements_hashtable[elem].length;ii++){
234 				var row = '<tr><td>' + elements_hashtable[elem][ii] + '</td></tr>';
235 				$(context).children('table').append(row);
236 			}
237 			*/
238 			
239 			$(context).show();
240 			//notifyUser($('#' + 'sc' +  i + '-' + j).find('.index-input').val(), 'debug');
241 			//$('#' + 'sc' +  i + '-' + j).find('.index-input').focus();
242 			
243 			$('#' + 'sc' +  i + '-' + j).find('.index-input').blur(function(){
244 				$(context).hide();
245 			});
246 			$('#' + 'sc' +  i + '-' + j).find('.index-input').focusin(function(){
247 				$(context).hide();
248 			});
249 			
250 			
251 					/*
252 			var t = '<div><table><tr><td><a href="">aaaa<a></td></tr><tr><td><a href="">bbbb<a></td></tr><table></div>';
253 			var x = '<div class="index_context"><table></table></div>';
254 			$('.focused','#searchclauselist').find('.index_search').append(x);
255 
256 			
257 			for(var i=0;i< elements_hashtable[elem].length;i++){
258 				var li = '<span><a href="">' + elements_hashtable[elem][i] + '</a></span>';
259 				$('.focused','#searchclauselist').find('.index_context').append(li);
260 			}*/
261 			
262 			//$('.focused','#searchclauselist').find('.index-input').hide();
263 			
264 		};
265 		
266 		//autocomplete
267 		function findValue(e) {
268 			//if( li == null ) return alert("No match!");
269 		 
270 			var sValue = e.selectValue;
271 			
272 			var i = $('.focused','#searchclauselist').closest('.sc-i').attr("id");
273 			var j = $('.focused','#searchclauselist').closest('.sc-j').attr("id");
274 			//alert(i+ ',' + j + sValue);
275 			searchclauseset.searchclauses[i][j].index = sValue;
276 			
277 			handleTermsSelection(i,j,sValue);		
278 			
279 			//$('.focused','#searchclauselist').find('.value-input').focus();
280 			
281 		}
282 		 
283 		function selectItem(li) {
284 			findValue(li);
285 		}
286 
287 		$(this.GetIndexInput()).autocompleteArray(element_autocomplete,{
288 			autoFill:true,
289 			//width:150,
290 			onFindValue:findValue,
291 			onItemSelect:selectItem
292 		});
293 
294 		$('body').find('.ac_results').css({'z-index' : '1000'});
295 	 
296  };
297 
298 SearchClause.prototype.CreateSelect = function(str, element){
299 	var index_select = GetIndexSelect();
300 	var index_input  = GetIndexInput();
301 	index_select.show();
302 	var x = "<option value='" + str + "'>" + str + "</option>";
303 	$(element).each(function(){
304 		x = x+"<option value='" + $(this).text() + "'>" + $(this).text() + "</option>";
305 	});
306 	index_select.html(x);
307 	
308 	index_input.hide();
309 	
310 };
311 
312 SearchClause.prototype.PlainText = function(){ 
313 	if (this.index.trim().length == 0 || this.value.trim().length == 0){
314 		return "";
315 	}
316 	if (this.is_category){
317 			return "ISOCAT( " + this.category + ") " + this.relation + " " + this.value;
318 	}
319 	return this.index.replace(" ","_") + " " + this.relation + " " + this.value;
320 };
321 /*
322 Searchclause.prototype.submit = function () {
323 	
324 	var uri = link('recordset','htmltable', this.query_uri());
325 	notifyUser("submitting query:" +  uri);
326 	
327 	this.container.find('.result').load( uri, function() {
328 				notifyUser("result-loaded");
329 				var get = $(this).parent().find('.cmd_get');				
330 
331 				get.removeClass('cmd_get');
332 				get.addClass('cmd_up');
333 				// get.show();
334 			});
335 
336 }
337 */
338     
339 $('.index-context td').live('click',  function(event) {
340 	
341 	searchclauseset.updatedata(this.textContent, false, "");
342 	//$('.focused','#searchclauselist').find('.index-input').val(	this.textContent);
343 	$('.focused','#searchclauselist').find('.index-context').hide();
344 	
345 	$('.focused','#searchclauselist').find('.value-input').focus();
346 
347 });
348 
349 
350 var searchclauseset_container = $("#searchclauselist"); 
351 
352 /**
353  * container of actually used Searchclauses in the query-input
354  * with basic functionality for building container - add, remove, clear
355  * and conversion functions - conversions from-to querystring
356  * @constructor 
357  */
358 
359 var searchclauseset = {
360 		/**  @field */
361 	searchclauses: [],
362 	container: '#searchclauselist',
363 	sctext: '',
364 		
365 	addsearchclause: function (searchclause, rel, _i, _j){
366 		var i,j;
367 			
368 		if (this.searchclauses.length == 0){
369 			i = 0;
370 			this.searchclauses[i] = new Array();
371 			j = 0;
372 		} else {
373 			if (rel == "or") {
374 				i = _i;//this.searchclauses.length - 1;
375 				j = this.searchclauses[i].length;//this.searchclauses[this.searchclauses.length - 1].length;
376 			} else {
377 				i = this.searchclauses.length;
378 				this.searchclauses[i] = new Array();
379 				j = 0;
380 			}
381 		}
382 		searchclause.i = i;
383 		searchclause.j = j;
384 		searchclause.listid = "sc" + i + "-" + j;
385 		this.searchclauses[i][j] = searchclause;
386 		
387 		searchclause.render(rel);						
388 		//$('#querylist').html(this.render());
389 	},
390 	
391 	fillFromURL: function(url){
392 	},
393 	
394 	clear: function() {
395 		notifyUser("clear query",'debug');
396 		
397 		for (var i = this.searchclauses.length - 1; i > -1; i--){
398 			for (var j = this.searchclauses[i].length - 1; j > -1; j--){
399 			
400 
401 					this.searchclauses[i].splice(j, 1);
402 					if  (j == 0){
403 						this.searchclauses.splice(i, 1);
404 						$('#' + 'sc' +  i + '-' + j).parent().parent().remove();
405 						$('#' + 'sc' +  i + '-' + j).parent().remove();
406 					} else {
407 						$('#' + 'sc' +  i + '-' + j).parent().remove();
408 					}		
409 		
410 			
411 			}
412 		}
413 		
414 		notifyUser(this.searchclauses.length,'debug');
415 		
416 		// reset focused
417 		//$('.sc-wrapper','#searchclauselist').each(function(){
418 		//	$(this).removeClass("focused");
419 		//});
420 		//$('#sc0-0').addClass("focused");
421 	},
422 	
423 	//TODO remove only last items
424 	removesearchclause: function (i, j) {
425 		notifyUser("removing sc:"  + i + "," + j,'debug');
426 		
427 		if ((j == 0) && (i == 0)){
428 			searchclauseset.searchclauses[i][j].index = "";
429 			searchclauseset.searchclauses[i][j].is_category = false;
430 			searchclauseset.searchclauses[i][j].relation = "=";
431 			searchclauseset.searchclauses[i][j].value = "";
432 			$('#sc0-0').find('.index-input').val("");
433 			$('#sc0-0').find('.rel_input').val("=");
434 			$('#sc0-0').find('.value-input').val("");			
435 			notifyUser("cannot remove", 'debug');
436 			return;
437 		}
438 		if ((j == 0) && (i < this.searchclauses.length - 1)){
439 			notifyUser("cannot remove",'debug');
440 			return;
441 		}
442 		if (j < this.searchclauses[i].length - 1){
443 			notifyUser("cannot remove",'debug');
444 			return;
445 		}
446 		this.searchclauses[i].splice(j, 1);
447 		 
448 		if  (j == 0){
449 			this.searchclauses.splice(i, 1);
450 			$('#' + 'sc' +  i + '-' + j).parent().parent().remove();
451 		} else {
452 			$('#' + 'sc' +  i + '-' + j).parent().remove();
453 		}		
454 
455 		notifyUser("sc removed, new sc.length:"  + this.searchclauses.length,'debug');
456 		for (var j = 0; j < this.searchclauses.length; j++) {
457 			notifyUser("  new sc.or_length:"  + this.searchclauses[j].length,'debug');
458 		}	
459 		// reset focused
460 		$('.sc-wrapper','#searchclauselist').each(function(){
461 			$(this).removeClass("focused");
462 		});
463 		$('#sc0-0').addClass("focused");
464 		
465 	},
466 	/*
467 	// just test
468 	load: function(i,j,rel,data) {
469 		if (data["triple"] == null ) {
470 			 this.searchclauses[i][j].index =  data["searchClause"]["index"];
471 			 this.searchclauses[i][j].relation =  data["searchClause"]["relation"]["value"];
472 			 this.searchclauses[i][j].value =  data["searchClause"]["term"];
473 			 
474 			 $('#sc0-0','#searchclauselist').find('.index-input').val(data["searchClause"]["index"]);
475 			 $('#sc0-0','#searchclauselist').find('.relation_input').val(data["searchClause"]["relation"]["value"]);
476 			 $('#sc0-0','#searchclauselist').find('.value-input').val(data["searchClause"]["term"]);
477 		 } else {
478 			 if (data["triple"]["boolean"]["value"] == "and") {
479 				 //var sc = new SearchClause("","","");
480 				 //this.addsearchclause(sc, "and", _i, _j)
481 				 // update(leftO)
482 				 // update(rightO)
483 				 load(i,j,"",data["triple"]["leftOperand"]);
484 				 i = this.searchclauses.length;
485 				 load(i+1,j,"and",data["triple"]["rightOperand"]);
486 			 } else {
487 				 load(i,j,"",data["triple"]["leftOperand"]);
488 				 j = this.searchclauses[i].length;
489 				 load(i,j,"or",data["triple"]["rightOperand"]);
490 			 }
491 			 
492 		 }
493 		 
494 	},
495 	*/
496 	// from querystring to searchclauses
497 	buildfromquerystring: function(){
498 		var arr = Query.simplequerystring(this.sctext);
499 		var sc, screl;
500 		
501 		this.clear();
502 		
503 		//notifyUser(arr,'debug');
504 		var arr_and = arr.split(" and ");
505 		screl = "";
506 		for( var i=0;i<arr_and.length;i++){
507 			var scstring = $.trim(arr_and[i]);
508 			
509 			if (scstring.substring(0,1) == "(" && scstring.substring(scstring.length-1) == ")") {
510 				scstring = scstring.substring(1,scstring.length-1);
511 				scstring = $.trim(scstring);
512 			}
513 			
514 			//notifyUser("i:" + scstring,'debug');
515 			var arr_or = scstring.split(" or ");
516 			if (i > 0) {screl = "and";}
517 			for( var j=0;j<arr_or.length;j++){
518 				var scstr = $.trim(arr_or[j]);
519 				
520 				// parse rel
521 				//notifyUser("j:"+scstr,'debug');
522 				var n;
523 				var scarr;
524 				var rel;
525 				n = scstr.indexOf("=");
526 				if (n > 0){
527 					scarr = scstr.split("=");
528 					rel = "=";
529 				}
530 				n = scstr.indexOf(" any ");
531 				if (n > 0){
532 					scarr = scstr.split(" any ");
533 					rel = "any";
534 				}
535 				n = scstr.indexOf(" contains ");
536 				if (n > 0){
537 					scarr = scstr.split(" contains ");
538 					rel = "contains";
539 				}
540 				n = scstr.indexOf("<");
541 				if (n > 0){
542 					scarr = scstr.split("<");
543 					rel = "<";
544 				}
545 				n = scstr.indexOf(">");
546 				if (n > 0){
547 					scarr = scstr.split(">");
548 					rel = ">";
549 				}
550 				n = scstr.indexOf(" all ");
551 				if (n > 0){
552 					scarr = scstr.split(" all ");
553 					rel = "all";
554 				}
555 				//var simplecalusetext = scstr.split("");
556 				if (j > 0) {screl = "or";}
557 				//notifyUser("scarr:" + scarr[0] + scarr[1],'debug');
558 				if (scarr == undefined) {
559 					sc = new SearchClause('','','');
560 				} else {
561 					sc = new SearchClause(scarr[0],rel,scarr[1]);
562 				}
563 
564 				this.addsearchclause(sc,screl,i,j);
565 				//searchclauseset.addsearchclause(sc,screl,i,j);
566 			}
567 		}
568 	},
569 	
570 	// from searchclauses to querystring
571 	buildsctext: function(){
572 	   var uncompletequery = false;
573 	   var ptext = "";
574 	   
575 		if ($('.searchtype_text','#search').size() > 0) {
576 			this.sctext = $('#query_area').val();
577 		} else {
578 		
579 			this.sctext = "";
580 			for (var i = 0; i < this.searchclauses.length; i++) {
581 				if ( i>0) this.sctext = this.sctext + " and ";
582 				this.sctext = this.sctext + " ( ";
583 				for (var j = 0; j < this.searchclauses[i].length; j++) {
584 					if ( j>0) this.sctext = this.sctext + " or ";
585 					this.sctext = this.sctext + " ( ";
586 					ptext = this.searchclauses[i][j].PlainText();
587 					if (ptext.length == 0){
588 						uncompletequery = true;
589 					}
590 					this.sctext = this.sctext + ptext;
591 					this.sctext = this.sctext + " ) ";
592 				}
593 				this.sctext = this.sctext + " ) ";
594 			}
595 			
596 		}	
597 		if (uncompletequery){
598 			this.sctext = "";
599 		}
600 		notifyUser(this.sctext);
601 	},
602 	
603 	updatedata: function(term, is_category, category, value) {	 
604 		
605 		var i = $('.focused','#searchclauselist').closest('.sc-i').attr("id");
606 		var j = $('.focused','#searchclauselist').closest('.sc-j').attr("id");
607 		
608 		if (term != undefined){
609 			$('.focused','#searchclauselist').find('.index-input').val(term);
610 			searchclauseset.searchclauses[i][j].index = term.replace(" ","_");
611 		}
612 		
613 		if (value == undefined){
614 			$('.focused','#searchclauselist').find('.value-input').focus();
615 		} else {
616 			$('.focused','#searchclauselist').find('.value-input').val(value);
617 			searchclauseset.searchclauses[i][j].value = value;
618 		}
619 		
620 		if (is_category) {
621 			searchclauseset.searchclauses[i][j].is_category = true;
622 			searchclauseset.searchclauses[i][j].category = category;
623 		} else {
624 			searchclauseset.searchclauses[i][j].is_category = false;
625 		}
626 		
627 	},
628 	
629 	initAutocomplete: function() {
630 		notifyUser("init autocomplete",'debug');
631 		
632 		for (var i = this.searchclauses.length - 1; i > -1; i--){
633 			for (var j = this.searchclauses[i].length - 1; j > -1; j--){
634 					this.searchclauses[i][j].initAutocomplete();		
635 			}
636 		}	
637 	}
638 };
639 
640