1 /*
  2 ---
  3 
  4 description: A content assist for textares of your webpage.
  5 
  6 license: GNU General Public License, version 2.
  7 
  8 authors:
  9 - Andrea Dessì <nkjoep@gmail.com>
 10 
 11 requires:
 12  core/1.3:
 13   - all
 14  more/1.3:  
 15   - all (sorry I should investigate about which More classes are needed.) 
 16 
 17 provides: [MooContentAssist]
 18 
 19 ...
 20 */
 21 /*
 22 Changelog:
 23 14 Mar 2011 v0.80.3 - namespace parser, fixed "charAt()" problem with IE7
 24 08 Mar 2011 v0.80.2 - namespace parser, now with allowed chars (or strings) in the namespace
 25 08 Mar 2011 v0.80.1 - configurable items container inside the main box
 26 06 Mar 2011 v0.80 - MooTools 1.3, several bugfixing, internal API rewritten.
 27 01 Jul 2010 v0.70.4 - converter from xml to words object, fixed bug on foundlist, fixed bug on assist window position
 28 27 Jun 2010 v0.70 - theme changer, new demo with theme toggler
 29 11 Jun 2010 v0.70 - configurable number of item shown in the box
 30 10 Jun 2010 v0.70 - scrollable result box, scrollable result box shows always the current item in the middle
 31 04 Jun 2010 v0.68 - few standard methods for positioning, css rules methods
 32 24 May 2010 v0.68 - fixed textarea scroll when inserting keywords, fixed assistWindow position
 33 23 May 2010 v0.66 - first dot fixed, occurence text highlight fixed, animation now is a parameter
 34 22 May 2010 v0.64 - ie7 fixes
 35 21 May 2010 v0.63 - added events "click" and "over" to the shown items, when showing assistWindow first item is already selected, added "." trigger
 36 21 May 2010 v0.60 - added styles for items, window positioning 
 37 20 May 2010 v0.55 - fixed textarea events
 38 16 May 2010 v0.25 - fixed words data structure
 39 15 May 2010 v0.15 - added completed text, events and keys
 40 13 May 2010 v0.0  - hello word	
 41 */
 42 /* JSHint globals
 43 globals Events: false, Options: false, MooTools: false, Class: false, Element: false, typeOf: false, instanceOf: false, Fx: false, Slick: false, Type: false, Chain: false, Elements: false, Document: false, Event: false, Window: false, Browser: false , Request: false, Keyboard: false */
 44 
 45 /**
 46  * @author Andrea Dessì <nkjoep@gmail.com>
 47  * @fileoverview A content assist for textares of your webpage. {@link https://github.com/NKjoep/MooContentAssist}
 48  * @version 0.80.3
 49  * 
 50  * */
 51 /**
 52 Construct a new MooContentAssist object.
 53 @class {MooContentAssist} This is the basic MooContentAssist class. It adds a content/code assist functionality to your textareas.
 54 @constructor
 55 @param {Object} options
 56 @param {HtmlElement} options.source The html element of an input or textarea.
 57 @param {Integer} [options.frameSize=3] How many items show in the window at same size.
 58 @param {Integer} [options.animationDuration=75] How long the show/hide animation in milliseconds
 59 @param {Object} options.vocabulary The JSON obj representing the vocabulary
 60 @param {Boolean} [options.vocabularyDiscoverer=true] Toggle the automatic words discoverer on/off
 61 @param {String} [options.vocabularyUrl] The url for ajax calls. It's optional.
 62 @param {String} [options.vocabularyUrlParam="ns"] The name of the querystring variable passed to the options.vocabularyUrl in ajax calls
 63 @param {String} [options.vocabularyUrlMethod="get"] Supported: get, post. Used for ajax calls.
 64 @param {Object} [options.windowPadding={x: 0, y: 2}] The margin of the assist window. It's an object with x,y keys.
 65 @param {String} [options.itemType="li"] The tag used for generating itemsContainer
 66 @param {String} [options.itemsContainer="ul"] The tag used for generating the items container
 67 @param {String} [options.matchedTextItemType="span"] The tag used for the matched text
 68 @param {Boolean} [options.aggressiveAssist="true"] Toggle aggressiveAssist mode on/offset
 69 @param {String[]} [options.namespaceAllowed=["()", "$"]] Define which strings can be contained in namespaces item.
 70 @param {Object} [options.css]
 71 @param {String} [options.css.item="item"] The css class for the single item
 72 @param {String} [options.css.itemsContainer="itemsContainer"] The css for the items container
 73 @param {String} [options.css.itemSelected="itemSelected"] The css class added to the item when it's selected
 74 @param {String} [options.css.messageItem="message"]
 75 @param {String} [options.css.matchedText="matched"]
 76 @param {Object} [options.labels]
 77 @param {String} [options.labels.nothingFound="Nothing was found."]
 78 @param {String} [options.labels.ajaxError="Error while retrieving data."]
 79 @param {Function} [options.vocabularyManager_GetVocabulary]
 80 @param {Function} [options.vocabularyManager_Extract]
 81 @param {Function} [options.vocabularyManager_Render]
 82 @return {MooContentAssist} A MooContentAssist
 83 * 
 84 */
 85 var MooContentAssist = new Class({
 86 	version: "MooContentAssist v0.80.3",
 87 	Implements: [Events, Options],
 88 	options: {
 89 		source: null,
 90 		frameSize: 3,
 91 		animationDuration: 75,
 92 		vocabulary: null,
 93 		vocabularyDiscoverer: true,
 94 		vocabularyUrl: null,
 95 		vocabularyUrlParam: "ns",
 96 		vocabularyUrlMethod: "get",
 97 		windowPadding: {x: 0, y: 2},
 98 		itemType: "li",
 99 		itemsContainerType: "ul",
100 		matchedTextItemType: "span",
101 		aggressiveAssist: true,
102 		namespaceAllowed: ["()", "$"],
103 		css : {
104 			item: "item",
105 			itemsContainer: "itemsContainer",
106 			itemSelected: "itemSelected",
107 			messageItem: "message",
108 			matchedText: "matched"
109 		},
110 		labels: {
111 			nothingFound: "Nothing was found.",
112 			ajaxError: "Error while retrieving data."
113 		},
114 		vocabularyManager_Render: function(obj) {
115 			var ns = this.getNameSpace().getLast();
116 			var rendered = new Element(this.options.itemType,{"class": this.options.css.item});
117 			rendered.store("value",obj);
118 			if (ns!="/") {
119 				new Element(this.options.matchedTextItemType,{text: obj.substring(0,ns.length), "class": this.options.css.matchedText}).inject(rendered);
120 				obj=obj.substring(ns.length);
121 			}
122 			rendered.appendText(obj);						
123 			return rendered; 
124 		},
125 		vocabularyManager_Extract: function(namespace,vocabulary) {
126 			if (namespace[0] === "") { 
127 				namespace=Array.clone(namespace);
128 				namespace.shift(); 
129 			}
130 			var vocabularyFound = [];
131 			var found = null;
132 			var searchKey = null;
133 			if (namespace.length === 1){
134 				found = vocabulary; 
135 				if (namespace[0] != "/") {
136 					searchKey = namespace[0];
137 						searchKey = searchKey.replace(/\*/g,"\\\*");
138 						searchKey = searchKey.replace(/\./g,"\\\.");
139 						searchKey = searchKey.replace(/\?/g,"\\\?");
140 						searchKey = searchKey.replace(/\[/g,"\\\[");
141 						searchKey = searchKey.replace(/\]/g,"\\\]");
142 						searchKey = searchKey.replace(/\(/g,"\\\(");
143 						searchKey = searchKey.replace(/\)/g,"\\\)");
144 						searchKey = searchKey.replace(/\{/g,"\\\{");
145 						searchKey = searchKey.replace(/\}/g,"\\\}");
146 						searchKey = searchKey.replace(/\^/g,"\\\^");
147 						searchKey = searchKey.replace(/\$/g,"\\\$");
148 				}
149 			}
150 			else if (namespace.length > 1) {
151 				if (namespace[namespace.length-1] != "/") {
152 					searchKey = namespace[namespace.length-1];
153 						searchKey = searchKey.replace(/\|/g,"\\\|");
154 						searchKey = searchKey.replace(/\*/g,"\\\*");
155 						searchKey = searchKey.replace(/\./g,"\\\.");
156 						searchKey = searchKey.replace(/\?/g,"\\\?");
157 						searchKey = searchKey.replace(/\[/g,"\\\[");
158 						searchKey = searchKey.replace(/\]/g,"\\\]");
159 						searchKey = searchKey.replace(/\(/g,"\\\(");
160 						searchKey = searchKey.replace(/\)/g,"\\\)");
161 						searchKey = searchKey.replace(/\{/g,"\\\{");
162 						searchKey = searchKey.replace(/\}/g,"\\\}");
163 						searchKey = searchKey.replace(/\^/g,"\\\^");
164 						searchKey = searchKey.replace(/\$/g,"\\\$");
165 				}
166 				namespace=Array.clone(namespace);
167 				namespace.pop();
168 				var tempFound = vocabulary;
169 				for (var i=0;i<namespace.length;i++) {
170 					try {
171 						tempFound = tempFound[namespace[i]];
172 					}
173 					catch (e) {
174 						tempFound = null;
175 					}
176 				}
177 				found = tempFound;
178 			}
179 			if (null !== found) {
180 				if(typeOf(found)=="object") {
181 					Object.each(found,function(value,key){
182 						if (searchKey === null || key.test("^"+searchKey,"i")) {
183 							vocabularyFound.push(key);
184 						}
185 					});
186 				}
187 				else if(typeOf(found)=="array") {
188 					Array.each(found,function(item,index,object) {
189 						if(typeOf(item)=="string" || typeOf(item)=="number") {
190 							item = item.toString();
191 							if (item.length>0) {
192 								if (searchKey === null || item.test("^"+searchKey,"i")) {
193 									vocabularyFound.push(item.toString());
194 								}	
195 							}
196 						}
197 					});
198 				}
199 				vocabularyFound.sort();
200 			}
201 			return vocabularyFound;
202 		},
203 		vocabularyManager_GetVocabulary: function(namespace) {
204 			var currentNamespace = namespace;
205 			this._currentVocabulary = null;
206 			var extractedVocabulary = null;
207 			if (typeOf(this.options.vocabularyUrl)=="string") {
208 				var namespaceData = this.options.vocabularyUrlParam+"="+currentNamespace.join("."); 
209 				if (this.vocabularyRequest===undefined) {
210 					this.vocabularyRequest = new Request.JSON({
211 						secure: true,
212 						url: this.options.vocabularyUrl,
213 						method: this.options.vocabularyUrlMethod,
214 						//data: namespaceData,
215 						async: false,
216 						link: "cancel",
217 						onSuccess: function(obj) {
218 							this.currentVocabulary = obj;
219 						},
220 						onFailure: function(xhr) {
221 							var messageEl = this._createMessage(this.options.labels.ajaxError);
222 							this.setAssistWindowContent(messageEl);
223 						}.bind(this)
224 					});
225 				}
226 				else {
227 					this.vocabularyRequest.cancel();
228 				}
229 				this.vocabularyRequest.currentVocabulary = null;
230 				var reqObj = {};
231 				reqObj[this.options.vocabularyUrlParam] = currentNamespace;
232 				this.vocabularyRequest.send(namespaceData);
233 				extractedVocabulary = this.options.vocabularyManager_Extract.call(this,currentNamespace,this.vocabularyRequest.currentVocabulary);
234 			}
235 			else {
236 				extractedVocabulary = this.options.vocabularyManager_Extract.call(this,currentNamespace,this.options.vocabulary);
237 			}
238 			return extractedVocabulary;
239 		}
240 	},
241 	_checkFocus: function(target) {
242 		var t = target;
243 		var s = this.options.source;
244 		var assistWindow = this.getAssistWindow();
245 		var checkA = (assistWindow!==null) && (t == assistWindow || assistWindow.contains(t)); 
246 		var checkB = (t == s || s.contains(t));
247 		if (t==window) return false;
248 		else if (checkA || checkB) return true;
249 		else {
250 			return false;
251 		}
252 	},
253 	_createMessage: function(text) {
254 		var messageEl = new Element(this.options.itemType, {
255 			"class": this.options.css.messageItem,
256 			"text": text
257 		});
258 		return messageEl;
259 	},
260 	_discoverUserVocabulary: function(namespace) {
261 		var found = [];
262 		if (namespace.length==1) {
263 			namespace = namespace[0];
264 			var that = this;
265 		    found = this._discoverWords(this.getSourceValue());
266 			found = found.filter(function(item, index){
267 				var check = false;
268 				if (namespace == "/") {
269 					check = true;
270 				}
271 				else if (namespace==item) {
272 					check = false;
273 				}
274 				else if (item.substring(0,namespace.length).toLowerCase() == namespace.toLowerCase())  {
275 					check = true;
276 				}
277 				return check;
278 			});
279 			found.sort();
280 		}
281 		return found;
282 	},
283 	_discoverWords: function(str){
284 		str = str.replace(/\W/g," ").clean().split(" ").clean().unique();
285 		var tmp = [];
286 		str.each(function(item, index){
287 			if (item.length > 3) {
288 			tmp.push(item.clean());
289 			}
290 		});
291 		str = tmp;
292 		return str;
293     },
294 	_eventManager: function() {
295 		this.addEvents({
296 			"start": function(mca) { this.start(); }.bind(this),
297 			"end": function(mca) { this.end(); }.bind(this)
298 		});
299 		var myKeyboardEvents = new Keyboard({
300 	        active: false,
301 	        events: {
302 	            "alt+space": function(ev){
303 					if (this.getAssistWindow()!==null) {
304 						//("already assisting!");
305 						ev.preventDefault();
306 					}
307 					else {
308 						//("start assist");
309 		                ev.preventDefault();
310 						this.fireEvent("start",this);
311 					}
312 	            }.bind(this),
313 	            "control+space": function(ev){
314 					//("already assisting!");
315 					if (this.getAssistWindow()!==null) {
316 						ev.preventDefault();
317 						this.fireEvent("start",this);
318 					}
319 					else {
320 		                //("start assist");
321 		                ev.preventDefault();
322 						this.fireEvent("start",this);
323 					}
324 				}.bind(this),
325 				"up": function(ev) {
326 					//("select item up");
327 					if (this.getAssistWindow()!==null) {
328 						ev.preventDefault();
329 						this.selectItemUp();
330 					}
331 				}.bind(this),
332 				"down": function(ev) {
333 					//("select item down");
334 					if (this.getAssistWindow()!==null) {
335 						ev.preventDefault();
336 						this.selectItemDown();
337 					}
338 				}.bind(this),
339 				"esc": function(ev) {
340 					//("close it!");
341 					if (this.getAssistWindow()!==null) {
342 						ev.preventDefault();
343 						this.fireEvent("end",this);
344 					}
345 				}.bind(this),
346 				"tab": function(ev) {
347 					//("close it!");
348 					if (this.getAssistWindow()!==null) {
349 						ev.preventDefault();
350 						this.fireEvent("end",this);
351 					}
352 				}.bind(this),
353 				"enter": function(ev) {
354 					//("use the item! and destroy it!");
355 					if(this.getAssistWindow()!==null && this.getItemSelected()!==null) {
356 						ev.preventDefault();
357 						this._useItemSelected();
358 						this.fireEvent("end",this);
359 					}
360 				}.bind(this),
361 				"keyup:delete": function(ev){ 
362 					if(this.getAssistWindow()!==null) {
363 						this.fireEvent("start",this); 
364 					}
365 				}.bind(this),
366 				"keyup:cancel": function(ev){ 
367 					if (this.getAssistWindow()!==null) {
368 						this.fireEvent("start",this); 
369 					}
370 				}.bind(this),
371 				"keyup:backspace": function(ev){ 
372 					if (this.getAssistWindow()!==null) {
373 						this.fireEvent("start",this);
374 					}
375 				}.bind(this),
376 				"keyup:space": function(ev){ 
377 					this.fireEvent("end",this);
378 				}.bind(this)
379 	        }
380 	    });
381 		var that = this;
382 		this.options.source.addEvents({
383 			"focus": function(ev) {
384 				this[1]._setSourceCaretPosition();
385 				this[0].activate();				
386 			}.bind([myKeyboardEvents,this]),
387 			"blur": function(ev) {
388 				this[0].deactivate();
389 				if (!this[1]._checkFocus(ev.target)) {
390 					this[1].fireEvent("end",this[1]);
391 				}
392 			}.bind([myKeyboardEvents,this]),
393 			"keyup": function(ev){
394 				this._setSourceCaretPosition();
395 				if (this.getAssistWindow()!==null||this.options.aggressiveAssist) {
396 					//removed control, for strange behaviour when selecting all with control+a
397 					if(!ev.control && ev.key.length == 1 && ev.key.test(/^\w$/)) {
398 						this.fireEvent("start",this);
399 					}
400 				}
401 			}.bind(this),
402 			"keypress": that._setSourceCaretPosition.bind(this),
403 			"keydown":  that._setSourceCaretPosition.bind(this)
404 		});
405 		this.options.source.set("autocomplete","off");
406         this.options.source.setProperty("autocomplete","off");
407 		window.addEvent("click",function(ev) {
408 			if (!this._checkFocus(ev.target)) {
409 				this.fireEvent("end",this);
410 			}
411 		}.bind(this));
412 	},
413 	_mergeVocabulary: function(vocabulary, vocabularyToInclude) {
414 		var merged = vocabulary.combine(vocabularyToInclude).sort();
415 		return merged;
416 	},
417 	_namespaceParser: function(nameSpaceString,caretPosition) {
418 		if (nameSpaceString===undefined) { nameSpaceString=this.getSourceValue(); }
419 		if (typeOf(caretPosition)!="number") {
420 			caretPosition=this.getSourceCaretPosition();
421 		}
422 		var namespace = [];
423 		var allowed  = this.options.namespaceAllowed;
424 		
425 		/* parser start */
426 		var positionStart = 0;
427 		var i = 0;
428 		for (i=caretPosition-1;i>0;--i) {
429 			var character = nameSpaceString.charAt(i);
430 			var previousCharacter = nameSpaceString.charAt(i+1);
431 			if (character===undefined) {
432 				break;
433 			}
434 			if (character=="." && previousCharacter==".") {
435 				positionStart = i+1+1; 
436 				break; 
437 			}
438 			var cursorJump = 0;
439 			var endsWithAllowed = allowed.some(function(item) {
440 				if (item.length==1) {
441 					return character==item;
442 				}
443 				else if (nameSpaceString.substring(i-item.length+1,i+1)==item) {
444 					//cursorJump = item.length+1;
445 					cursorJump = item.length-1;
446 					return true;
447 				}
448 				else if (nameSpaceString.substring(i,i+item.length)==item) {
449 					return true;
450 				}
451 			});
452 			if (cursorJump>0) {
453 				i = i-cursorJump;
454 				character=nameSpaceString[i];
455 				previousCharacter=nameSpaceString.charAt(i+1);
456 				continue;
457 			}
458 			if ( character!="." && !(character.test(/^\w$/) || endsWithAllowed ) ) {
459 				positionStart = i+1;
460 				if (previousCharacter!==undefined) {
461 					var jumpPrevious = 0;
462 					if (previousCharacter==".") {
463 						//if theres a dot ".", just move forward of 1 position and exit the loop.
464 						jumpPrevious = 1;
465 						positionStart = i+1+jumpPrevious;
466 						break;
467 					}
468 					var previousCharacterEndsWithAllowed = allowed.some(function(item) {
469 						if (item.length==1) {
470 							if (previousCharacter==item) {
471 								jumpPrevious=1;
472 								return true;	
473 							}
474 						} 
475 						//forward seek
476 							else if (nameSpaceString.substring(i,i+item.length) == item ) {
477 								jumpPrevious=item.length;
478 								return true;
479 							}
480 						//back seek
481 							else if (nameSpaceString.substring(i-item.length+1,i+1) == item) {
482 								jumpPrevious= (-(item.length));
483 								return true;
484 							}
485 					});
486 					
487 					if (!previousCharacterEndsWithAllowed && !previousCharacter.test(/^\w$/)) { 
488 						//here only allowed
489 						positionStart = i+1+jumpPrevious;
490 					} 
491 				}
492 				break;
493 			}
494 		}
495 		if(positionStart>caretPosition) {
496 			positionStart=caretPosition;
497 		}
498 		nameSpaceString = nameSpaceString.substring(positionStart,caretPosition).trim();
499 		if (nameSpaceString.length>0) {
500 			namespace=nameSpaceString.split(".");
501 			if (namespace[namespace.length-1]==="") {
502 				namespace[namespace.length-1] = "/";
503 			}
504 		}
505 		else {
506 			namespace=["/"];
507 		}
508 		return namespace;	
509 		/* parser end */
510 	},
511 	_setItemSelected: function(item, executeScroll) {
512 		if (item!==null) {
513 			var oldItem = this.getItemSelected();
514 			if (oldItem!==null) { oldItem.removeClass(this.options.css.itemSelected); }
515 			item.addClass(this.options.css.itemSelected);
516 			this.fireEvent("selectItem",item);
517 			if (executeScroll !== false) {
518 				this.scrollToItem(item);
519 			}
520 		}
521 	},
522 	_setSourceCaretPosition: function() {
523 		this.options.source.store("MooContentAssist-CaretPosition",this.options.source.getCaretPosition());
524 	},
525 	_useItemSelected: function() {
526 		var text = this.getItemSelected();
527 		var w = this.getAssistWindow();
528 		if (text!==null && w!==null) {
529 			text = text.retrieve("value");
530 			var textarea = this.options.source;
531 			var scrollTop = textarea.scrollTop;
532 			var position = this.getSourceCaretPosition();
533 			var namespace = this.getNameSpace().getLast();
534 			var completedText=null;
535 			if (namespace=="/") {
536 				completedText=text;
537 			}
538 			else {
539 				completedText = text.substring(namespace.length,text.length);
540 			}
541 			var adjustCaseText = text.substring(0,text.length-completedText.length);
542 			var textbefore = textarea.get("value").substring(0, position);
543 			textbefore = textbefore.substring(0,textbefore.length-adjustCaseText.length)+adjustCaseText;
544 			var textafter = textarea.get("value").substring(position);
545 			textarea.set("value", textbefore + completedText + textafter);
546 			textarea.setCaretPosition(textbefore.length + completedText.length);
547 			textarea.scrollTop = scrollTop;
548 			this.fireEvent("useItem",text);
549 			this.fireEvent("end",this);
550 		}
551 	},
552 	createAssistWindow: function() {
553 		var w = new Element("div",{
554 			"class": "MooContentAssist"
555 		});
556 		
557 		this.options.source.store("MooContentAssist",w);
558 		var itemsEventsObj = {};
559 			itemsEventsObj['click:relay(.'+this.options.css.item+')'] = function(ev){ 
560 				ev.stopPropagation();
561 				ev.preventDefault();
562 				this._useItemSelected();
563 			}.bind(this);
564 			itemsEventsObj['mouseover:relay(.'+this.options.css.item+')'] = function(ev){ 
565 				ev.stopPropagation();
566 				ev.preventDefault();
567 				if (ev.target.get("tag")==this.options.itemType&&ev.target.hasClass(this.options.css.item)) {
568 					this._setItemSelected(ev.target,false);
569 				}
570 				else {
571 					var parent = ev.target.getParent(this.options.itemType+"."+this.options.css.item);
572 					if (parent!==null) {
573 						this._setItemSelected(parent,false);
574 					}
575 					
576 				}
577 			}.bind(this);
578 		w.addEvents(itemsEventsObj);
579 		var sourceEl = this.options.source;
580 		var sourceElPosition=sourceEl.getPosition(); 
581 		var sourceElSize = sourceEl.getDimensions();
582 		w.inject(this.options.source,"after");
583 		var top = sourceElPosition.y+sourceElSize.height+this.options.windowPadding.y;
584 		var left= sourceElPosition.x+this.options.windowPadding.x;
585 		w.setStyles({
586 			"overflow": "auto",
587 			"width": sourceElSize.width,
588 			"left": left,
589 			"top": top
590 		});
591 		
592 		if (this.options.itemsContainerType!==null) {
593 			new Element(this.options.itemsContainerType, {
594 				"class": this.options.css.itemsContainer
595 			}).inject(w,"bottom");
596 		}
597 		return w;
598 	},
599 	end: function() {
600 		var mca = this.getAssistWindow();
601 		if (mca !== null) {
602 			mca.destroy();
603 			this.options.source.store("MooContentAssist",null);
604 		}
605 	},
606 	getAssistWindow: function() {
607 		return this.options.source.retrieve("MooContentAssist");
608 	},
609 	getItemSelected: function() {
610 		var w = this.getAssistWindow();
611 		var item = null;
612 		if (w!==null) {
613 			item = w.getElement(this._prefixItemsSelector+this.options.css.itemSelected);
614 		}
615 		return item;
616 	},
617 	getNameSpace: function(string) { 
618 		var namespace = [];
619 		namespace = this._namespaceParser(string);
620 		return namespace;
621 	},
622 	getRenderedWord: function(word) {
623 		return this.options.vocabularyManager_Render.call(this,word);
624 	},
625 	getSourceCaretPosition: function() {
626 		var pos = this.options.source.retrieve("MooContentAssist-CaretPosition");
627 		if (pos === null) {
628 			pos = this.options.source.getCaretPosition();
629 		}
630 		return pos;
631 	},
632 	getSourceValue: function() { 
633 		return this.options.source.get("value"); 
634 	},
635 	getVocabulary: function(namespace) {
636 		var extractedVocabulary = this.options.vocabularyManager_GetVocabulary.call(this,namespace);
637 		return extractedVocabulary;
638 	},
639 	hide: function() {
640 		if(this.getAssistWindow()!==null) this.getAssistWindow().dissolve();
641 		this.fireEvent("hide");
642 	},
643 	initialize: function(opt) {
644 		this.setOptions(opt);
645 		if (opt.itemsContainerType===null) {
646 			this.options.itemsContainerType = null;
647 			this._prefixItemsSelector=".";
648 		}
649 		else {
650 			this._prefixItemsSelector="."+this.options.css.itemsContainer+" .";
651 		}
652 		this.options.source.store("MooContentAssist",null);
653 		this._eventManager();
654 		this.oldNamespace=false;
655 	},
656 	scrollToItem: function(item) {
657 		var w = this.getAssistWindow();
658 		if (w!==null && item!==null) {
659 			var animationScroller = w.retrieve("MooContentAssist-AnimationScroller");
660 			if (animationScroller === null) {
661 				animationScroller = new Fx.Scroll(w,{
662 		            duration: this.options.animationDuration,
663 		            offset: {"x": 0, "y": w.getStyle('padding-top').toInt()*-1}
664 		        });
665 		        w.store("MooContentAssist-AnimationScroller",animationScroller);
666 			}
667 			//item height
668 		    var i = item.getComputedSize({"styles": ["margin","padding","border"]}).totalHeight;
669 		    //box height
670 		    var f = (w.getComputedSize({"styles": ["padding"]}).totalHeight/i).toInt();
671 		    //children
672 		    var children = w.getElements(this._prefixItemsSelector+this.options.css.item);
673 		    //current item
674 		    var c = children.indexOf(item);
675 		    //index
676 		    var indexToScrollTo = ((c/f).toInt()) * f; 
677 			//calculate the current "frame"
678 			if (c > (f/2).toInt()) {
679 				indexToScrollTo = c - (f/2).toInt(); 
680 			}
681 			//scroll to item at that index
682 			if(w.getElement(children[indexToScrollTo])!==null) {
683 				try {
684 					animationScroller.toElement(children[indexToScrollTo]);
685 				} catch(e) {
686 					//sometimes IE fires errors...
687 					w.store("MooContentAssist-AnimationScroller",null);
688 				}
689 			}
690 		}
691 	},
692 	selectItemDown: function() {
693 		var currentItem = this.getItemSelected();
694 		var prevItem = null;
695 		if (currentItem!==null) {
696 			prevItem = currentItem.getNext();
697 		}
698 		else {
699 			prevItem = this.getAssistWindow().getFirst(this._prefixItemsSelector+this.options.css.item);
700 		}
701 		if (prevItem!==null) { 
702 			this._setItemSelected(prevItem); 
703 		}
704 		else { 
705 			this._setItemSelected(this.getAssistWindow().getFirst(this._prefixItemsSelector+this.options.css.item)); 
706 		}	
707 	},
708 	selectItemUp: function() {
709 		var currentItem = this.getItemSelected();
710 		var prevItem = null;
711 		if (currentItem!==null) {
712 			prevItem = currentItem.getPrevious();
713 		}
714 		else {
715 			prevItem = this.getAssistWindow().getLast(this._prefixItemsSelector+this.options.css.item);
716 		}
717 		if (prevItem!==null) { 
718 			this._setItemSelected(prevItem); 
719 		}
720 		else { 
721 			this._setItemSelected(this.getAssistWindow().getLast(this._prefixItemsSelector+this.options.css.item));
722 		}
723 	},
724 	setAggressiveAssist: function(aggressiveStatus) {
725 		if (typeOf(aggressiveStatus)=="boolean"){
726 			this.options.aggressiveAssist=aggressiveStatus;
727 		}
728 	},
729 	setAssistWindowContent: function(vocabulary) {
730 		var w = this.getAssistWindow();
731 		if (w!==null) {
732 			vocabulary = Array.from(vocabulary);
733 
734 			var injectBindElement = this.options.itemsContainerType===null? w : w.getElement("."+this.options.css.itemsContainer);
735 			var inject = function(word) {
736 				word.inject(this);
737 			}.bind(injectBindElement);
738 			for (var i=0;i<vocabulary.length;i++) {
739 				var currentWord = vocabulary[i];
740 				inject(currentWord);
741 			}
742 			this.setFrameSize();
743 			this.selectItemDown();
744 		}
745 	},
746 	setFrameSize: function(size) {
747 		if(typeOf(size) != "number") { size = this.options.frameSize; }
748 		var selector = this._prefixItemsSelector+this.options.css.item;
749 		var w = this.getAssistWindow();
750 		var children = w.getElements(selector);
751 		var childrenLength = children.length > 0 ? children.length : 1;
752 		if (childrenLength<size) { size = childrenLength;}
753 		var exampleItem = w.getElement(selector);
754 		if (exampleItem===null) {
755 			exampleItem = w.getElement(this._prefixItemsSelector+this.options.css.messageItem);
756 		}
757 		w.setStyle("height",(exampleItem.getComputedSize({
758 			"styles": ["padding","margin","border"]
759 		}).totalHeight * size) + "px");
760 	},
761 	show: function() {
762 		if(this.getAssistWindow()!==null) this.getAssistWindow().reveal();
763 		this.getAssistWindow().setStyle("opacity",1);
764 		this.fireEvent("show");
765 	},
766 	start: function() {
767 		var mca = this.getAssistWindow();
768 		if (mca!==null) {
769 			this.end();
770 			this.createAssistWindow();
771 		}
772 		var value = this.getSourceValue();
773 		var namespace = this.getNameSpace(value);
774 		var vocabulary = null;
775 		if (namespace == this.oldNamespace) {
776 			vocabulary = this.oldVocabulary;
777 		}
778 		else {
779 			vocabulary = this.getVocabulary(namespace);
780 			this.oldVocabulary = vocabulary;
781 		}
782 		if (this.options.vocabularyDiscoverer) {
783 			var userVocabulary = this._discoverUserVocabulary(namespace);
784 			vocabulary = this._mergeVocabulary(vocabulary,userVocabulary);
785 		}
786 		if (vocabulary.length > 0) {
787 			var renderedVocabulary = [];
788 			vocabulary.each(function(word) {
789 				renderedVocabulary.push(this.getRenderedWord(word));
790 			}.bind(this));
791 			if (mca === null) {
792 				mca = this.createAssistWindow();
793 			}
794 			this.setAssistWindowContent(renderedVocabulary);
795 		}
796 		else {
797 			if (!this.options.aggressiveAssist || (this.options.aggressiveAssist && namespace.length>1)) {
798 				var messageEl = this._createMessage(this.options.labels.nothingFound);
799 				if (mca === null) {
800 					mca = this.createAssistWindow();
801 				}
802 				this.setAssistWindowContent(messageEl);
803 			}
804 			else {
805 				this.end();
806 			}
807 		}
808 	}
809 });
810