//==============================
//AutoSuggestMenu version 1.1.0
//==============================


///////////////////////////////////////
//Class for rendering suggestions menu
///////////////////////////////////////

function AutoSuggestMenu()
{	
    //Constants
    var REFRESH_TYPE_COMPLETE       ="Complete";
    var REFRESH_TYPE_NEXT_PAGE      ="Next Page";
    var REFRESH_TYPE_PREVIOUS_PAGE  ="Previous Page";
    
    
    //Use self to handle events with specific object
    var self=this;
  
    //Properties
    self.id=null;
    self.textBoxID=null;
    self.hiddenSelectedValueID=null;
    
    self.minSuggestChars=1;
	self.maxSuggestChars=5;
	self.keyPressDelay=300;
    
    self.usePaging=true;
    self.pageSize=10;
    
    self.maxHeight=null;
   
    self.cssClass="asmMenu";
    self.menuItemCssClass="asmMenuItem";
    self.selMenuItemCssClass="asmSelMenuItem";
    self.navigationLinkCssClass="asmNavigationLink";
    
    self.useIFrame=true;
    self.updateTextBoxOnUpDown=true;
    self.resourceDir=null;
        
    self.menuItems=new Array();    //Array of AutoSuggestMenuItems
    self.onGetMenuItems=null;      //Overridable method to return suggestions
    self.onTextBoxUpdate=null;     //Overridable event handler that is called after textbox is updated with suggestion
            
    //Internal attributes
    var _dom=null;  	
    
	var _oldTextBoxValue="";
	var _selMenuItemIndex = null;	
	var _cancelSubmit=false;
    var _iFrame=null;
	var _keyPressTimer=null;
	var _onBlurTimer=null
    var _cancelOnBlur=false;
    var _hasVerticalScrollbar=false;
    
    var _pageIndex=0;
    var _totalResults=0;
    var _refreshType;
		
    //================================================
	//Private methods
	//================================================
	
			
	function getTextBoxCtrl()
	{
		return document.getElementById(self.textBoxID);
	}
		

    function getMenuItemsCount()
    {
        return self.menuItems.length;
    }
	
	
	function ensureMenuItemVisible(menuItemIndex)
	{  
	    TRACE("AutoSuggestMenu.ensureMenuItemVisible menuItemIndex=" + menuItemIndex + ", _hasVerticalScrollbar=" + _hasVerticalScrollbar);        
	   
	    if (!_hasVerticalScrollbar)
	        return;
	        	    
	    var menuStartY = _dom.scrollTop;
	    var menuEndY = _dom.scrollTop + _dom.offsetHeight;
	    	    
	    var menuItem=self.menuItems[menuItemIndex];
	    var menuItemDiv=menuItem.getDOM();
	    
	    var menuItemStartY = menuItemDiv.offsetTop;
	    var menuItemEndY = menuItemDiv.offsetTop + menuItemDiv.offsetHeight;
	
	    TRACE("AutoSuggestMenu.ensureMenuItemVisible menuStartY=" + menuStartY + ", menuEndY=" + menuEndY);        
	    TRACE("AutoSuggestMenu.ensureMenuItemVisible menuItemStartY=" + menuItemStartY + ", menuItemEndY=" + menuItemEndY);        
	
	    if (menuItemStartY < menuStartY)
	        _dom.scrollTop=menuItemStartY;
	    
	    if (menuItemEndY > menuEndY)
	        _dom.scrollTop=_dom.scrollTop + (menuItemEndY - menuEndY);
	}
	
	
	
	function moveUp()
	{
	    TRACE("AutoSuggestMenu.moveUp _selMenuItemIndex=" + _selMenuItemIndex);        
	    
	    if (_selMenuItemIndex==null)
		    itemIndex=getMenuItemsCount()-1;    //Select last item
		else
		    itemIndex=_selMenuItemIndex - 1;
		  		
		//Check if menu item exists
		if (itemIndex >= 0)
		{
            selectMenuItem(itemIndex, self.updateTextBoxOnUpDown);
            ensureMenuItemVisible(itemIndex);
		}
	}


	function moveDown()
	{
	    TRACE("AutoSuggestMenu.moveDown _selMenuItemIndex=" + _selMenuItemIndex);
		var itemIndex;
		
		if (_selMenuItemIndex==null)
		    itemIndex=0;
		else
		    itemIndex=_selMenuItemIndex + 1;
		
		if(itemIndex < getMenuItemsCount())
		{
			selectMenuItem(itemIndex, self.updateTextBoxOnUpDown);
			ensureMenuItemVisible(itemIndex);
		}
	}


	//Highlights menu item
	function highlightMenuItem(itemIndex)
	{
	    if (_selMenuItemIndex!=null)
	    {
		    if (_selMenuItemIndex==itemIndex)
			    return;    
	
	        //Unhighlight previously higlighted item
	        var menuItem=self.menuItems[_selMenuItemIndex];
		    menuItem.unhighlight();
		}
		
		var menuItem=self.menuItems[itemIndex];
        menuItem.highlight();
	}
	
	
	function selectMenuItem(itemIndex, updateTextBox)
	{
	    TRACE("AutoSuggestMenu.selectMenuItem itemIndex=" + itemIndex);
	      
	    highlightMenuItem(itemIndex);
        _selMenuItemIndex=itemIndex;
        
	    //Check if already selected
	    if ((updateTextBox==null) || (updateTextBox==true))
	    {
	        updateTextBoxValue();
	    }
  
	}

	
	function updateTextBoxValue()
	{
	    var menuItem=self.getSelectedMenuItem();   

        //Set selected value of control to the value of selected menu item
		self.setSelectedValue(menuItem.value);
     
        var preventUpdate=false;
        
        //Only call handler if it was specified
        if (self.onTextBoxUpdate)
        {  
            var evt=new TextBoxUpdateEvent();
            evt.source=self;
            evt.selMenuItem=menuItem;
            
            eval(self.onTextBoxUpdate + "(evt);");
            
            //Default text box update can be prevented if user calls evt.preventDefault
            preventUpdate=evt.getPreventDefault();
        }
        
        
        if (!preventUpdate)
        {
       	    //Update text box text	
		    var textBox=getTextBoxCtrl();
		    textBox.value = menuItem.value;
		}
    }
		

	function getTextBoxValue()
	{
		var textBox=getTextBoxCtrl();
		return(textBox.value);
	}
	
	
	function focusOnTextBox()
	{
	    //Clear out the timer that hides the menu
	    window.clearTimeout(_onBlurTimer);
		_onBlurTimer=null;
		
	    var textBox=getTextBoxCtrl();

	    if (XUtils.isIE())
	    {
	        //Send cursor to the end of the textBox
            var value = textBox.value;
            var textRange = textBox.createTextRange();
            
            textRange.moveStart('character', value.length);
            textRange.select();
	    }	
	    
	    textBox.focus();
	    
	}
	
	
	function isPreviousPageLinkEnabled()
	{
        var enabled=(_pageIndex!=0);
	    return enabled;
	}
	
	
	function isNextPageLinkEnabled()
	{
	    //Get number of menu items up to current page
	    var numMenuItems=(_pageIndex * self.pageSize) + self.menuItems.length;
	    
	    TRACE("AutoSuggestMenu.isNextPageLinkEnabled numMenuItems=" + numMenuItems + ", _totalResults=" + _totalResults);
	    if (numMenuItems < _totalResults)
	        return true;
	    else
	        return false;
	}
	
	
	function renderNavigationControlsMenuItem()
	{
	    var showPrev=isPreviousPageLinkEnabled();
	    var showNext=isNextPageLinkEnabled();
	    
	    TRACE("AutoSuggestMenu.renderNavigationControlsMenuItem showPrev=" + showPrev + ", showNext=" + showNext);
	    
	    if (!showPrev && !showNext)
	        return;
	   
        var div=XUtils.createElement("div");
        
        var table=XUtils.createElement("table");
        table.width="50px"
        
        var tbody=XUtils.createElement("tbody");
        var tr=XUtils.createElement("tr");

        //Left cell
        var td=XUtils.createElement("td");
        td.width="20px";
        td.align="left";
           
        if (showPrev)
        {
            var link=XUtils.createElement("a"); 
            link.className=self.navigationLinkCssClass;
            link.href="";
            
            link.innerHTML = "&lt;&lt;"
            link.onclick=self.onPreviousPage; 
            
            td.appendChild(link);
        }
        tr.appendChild(td);
                
        //Add a separator cell in the middle
        td=XUtils.createElement("td");
        td.width="10px";
        tr.appendChild(td);
    
    
        //Right cell
        td=XUtils.createElement("td");
        td.width="20px";
        td.align="right";
        
        if (showNext)
        {
           var link=XUtils.createElement("a"); 
           link.className=self.navigationLinkCssClass;
           link.href="";
           link.innerHTML="&gt;&gt;"
           link.onclick=self.onNextPage; 
          
           td.appendChild(link);
        }
       
        tr.appendChild(td);
       
        //Append table to div 
        tbody.appendChild(tr);
        table.appendChild(tbody);
        div.appendChild(table);
        
        //TRACE("AutoSuggestMenu.renderNavigationControlsMenuItem  div.innerHTML=" + div.innerHTML);
        _dom.appendChild(div); 
	}
	
	
	function createIFrame()
    {
        TRACE("AutoSuggestMenu.createIFrame");

        //Create a frame to make sure there is no    
        var iFrame=XUtils.createElement("IFRAME");
        
    	var blankPage=self.resourcesDir + "/Blank.html";
    	TRACE("AutoSuggestMenu.createIFrame blankPage=" + blankPage);

        iFrame.setAttribute("src",  blankPage);
        
        iFrame.style.position="absolute";
        iFrame.style.visibility="hidden";
        
        iFrame.style.left   = 0;
        iFrame.style.top    = 0;
        
        iFrame.style.width  = "0px";
        iFrame.style.height = "0px";
        
        return iFrame;   
    }
  
  
   	
   
	function renderMenuItems()
	{
	    TRACE("AutoSuggestMenu.renderMenuItems");

	    //Remove child divs
        while (_dom.childNodes[0])
        {
            _dom.removeChild(_dom.childNodes[0]);
        }   
        
        var menuItem;
        var menuItemDiv;
        var func;
        
        //Render menu items
	    for (count=0; count < self.menuItems.length; count++)
	    {
	        menuItem=self.menuItems[count];
	        
	        if (!menuItem.cssClass)
	            menuItem.cssClass=self.menuItemCssClass
	            
	        if (!menuItem.selCssClass)
	            menuItem.selCssClass=self.selMenuItemCssClass;
	    
	        //Assign parent menu and index to each item, 
	        //so they can call menu.onMenuItemMouseOver(); and menu.onMenuItemMouseClick();
	        menuItem.index  =count;
	        menuItem.menu   =self;
	        
	        menuItemDiv = menuItem.render();
            
            _dom.appendChild(menuItemDiv);    
	    }	   
	    
	    if (self.usePaging)
	    {
            renderNavigationControlsMenuItem();
	    }
	    
	    
	    
	    _hasVerticalScrollbar=false;
	    
	    //Update menu height
	    if (self.maxHeight)
	    {
	        var maxHeight=parseInt(self.maxHeight);
	        
	        _dom.style.height=null;
	         
	        TRACE("AutoSuggestMenu.renderMenuItems _dom.offsetHeight=" + _dom.offsetHeight + ", maxHeight=" + maxHeight);

	        if (_dom.offsetHeight > maxHeight)
	        {
	            _dom.style.height=maxHeight + "px";
	            _dom.scrollTop=0;
	            _hasVerticalScrollbar=true;
            }
	        
	        TRACE("AutoSuggestMenu.renderMenuItems _dom.style.height=" + _dom.style.height);
	    }	        
	}
	
    
    function refreshMenuItems(refreshType)
	{
	    if (!refreshType)
	        _refreshType=REFRESH_TYPE_COMPLETE;
	    else
	        _refreshType=refreshType;
	    
	    TRACE("AutoSuggestMenu.refreshMenuItems _refreshType=" + _refreshType);
	   
	    if (self.isVisible())
	        self.hide();
	    
	    //Get menu items
	    if (self.onGetMenuItems==null)
	        throw "Handler of AutoSuggestMenu.onGetMenuItems was not specified."
	    
	    var value=getTextBoxValue(); 
	    value=value.replace(/\"/, "\\\"");
		
		switch (_refreshType)
	    {
	        case REFRESH_TYPE_COMPLETE:
	            _pageIndex=0;
	            break;
	            
	        case REFRESH_TYPE_NEXT_PAGE:
	            _pageIndex++;
	            break;

	        case REFRESH_TYPE_PREVIOUS_PAGE:
	            _pageIndex--;
	            break;
	    }
	    
	    var func=self.onGetMenuItems + "(\"" + value + "\", " + 
	                                                    self.usePaging + ", " +
	                                                    _pageIndex + ", " + 
	                                                    self.pageSize + ", self.refreshMenuItemsCallback)";
	    TRACE("AutoSuggestMenu.refreshMenuItems func=" + func);
	    eval(func);
	}
	
		
	//This function is a continuation of refreshmenuItems
	self.refreshMenuItemsCallback = function(jsonData)
    {
        TRACE("AutoSuggestMenu.refreshMenuItemsCallback");
             
        var json=eval("(" + jsonData + ")");      
        var jsonMenuItem;
        var menuItem;
        
        //Clear out old menu items
        self.menuItems=new Array();
        
        for (count = 0; count < json.menuItems.length; count++)
        {
            jsonMenuItem=json.menuItems[count];
           
            menuItem=new AutoSuggestMenuItem();
            menuItem.label=jsonMenuItem.label;
            menuItem.value=jsonMenuItem.value;    
            
            if (jsonMenuItem.isSelectable!=null)
                menuItem.isSelectable=jsonMenuItem.isSelectable; 
                
            if (jsonMenuItem.cssClass!=null)
                menuItem.cssClass=jsonMenuItem.cssClass;   
            
            addMenuItem(menuItem);
        }
             
         TRACE("AutoSuggestMenu.refreshMenuItemsCallback getMenuItemsCount()=" + getMenuItemsCount());
       
        if (getMenuItemsCount() > 0)
        {
           if ((_refreshType==REFRESH_TYPE_COMPLETE) && self.usePaging)
            {
                //Save total number of available suggestions
                _totalResults=json.totalResults;
            }
            
            renderMenuItems();
	    	    
	        self.show();
	    }
    }
	
	
	function addMenuItem(menuItem)
	{   
	    self.menuItems[self.menuItems.length]=menuItem;
	}
	
		
    
    //================================================
	//Public methods
	//================================================
		
	self.setSelectedValue = function(value)
	{
		TRACE("AutoSuggestMenu.setSelectedValue value=" + value);
	
		var hdnSelectedValue=document.getElementById(self.hiddenSelectedValueID);
		hdnSelectedValue.value=value;
	}
    
    
    self.getSelectedValue = function()
	{
		TRACE("AutoSuggestMenu.getSelectedValue");
	
		var hdnSelectedValue=document.getElementById(self.hiddenSelectedValueID);
		return hdnSelectedValue.value;
	}
	

	self.getSelectedMenuItem = function()
    {
        TRACE("AutoSuggestMenu.getHighlightedMenuItem _selMenuItemIndex=" + _selMenuItemIndex);
		
        if (_selMenuItemIndex!=null)
            return self.menuItems[_selMenuItemIndex];
        else
            return null;
    }
	
			
	self.isVisible = function()
	{
	    if (!_dom)
	        return false;
	        
		if (_dom.style.visibility == 'hidden')
			return false;
		else
			return true;
	}
	    
    
    
    function updateIFrame()
    {
        _iFrame.style.left   = _dom.style.left;
        _iFrame.style.top    = _dom.style.top;
     
        _iFrame.style.width  = _dom.offsetWidth + "px";
        _iFrame.style.height = _dom.offsetHeight + "px";
	}
	
	
	
	self.show = function ()
	{
	    TRACE("AutoSuggestMenu.show _dom=" + _dom);
	
	    if (_dom == null)
            self.render();
            
        var textBox=getTextBoxCtrl();
        _dom.style.left	=XUtils.getAbsoluteLeft(textBox)+ "px";
        _dom.style.top	=XUtils.getAbsoluteTop(textBox) + textBox.offsetHeight + "px";
        
                
		if (_iFrame)
		{
		    updateIFrame();
		    
	        _iFrame.style.visibility="visible";
	    }
	   
	    _dom.style.visibility = "visible";
	}
	
				
	self.hide = function()
	{
	    TRACE("AutoSuggestMenu.hide");
	
	    if (!self.isVisible())
	    {
	        TRACE("AutoSuggestMenu.Hide already hidden");
	        return;
	    }
  	    
	    _selMenuItemIndex=null;
	  
	    _dom.style.visibility = "hidden";
	    
	    if (_iFrame)
	        _iFrame.style.visibility="hidden";
	}
	
    
    self.render = function()
	{
	    TRACE("AutoSuggestMenu.render");
	    
	    if (self.id==null)
	        throw "id is required.";
	    
	    if (self.textBoxID==null)
	        throw "textBoxID is required.";
	    
	     if (self.hiddenSelectedValueID==null)
	        throw "hiddenSelectedValueID is required.";
	   
	    var textBox=getTextBoxCtrl();
        
	    //Only render menu once. 
	    //After that just replace the menu Items.
	    var menuDiv;
        menuDiv = XUtils.createElement('div');
        
        menuDiv.id=self.id;
        menuDiv.className=self.cssClass;
        menuDiv.sourceObject=self;
        
        XUtils.addEventListener(menuDiv, "scroll",    self.onMenuScroll);
                    
     	TRACE("AutoSuggestMenu.render absoluteLeft=" + XUtils.getAbsoluteLeft(textBox) + ", absoluteTop=" + XUtils.getAbsoluteTop(textBox));
		
        //Move menu right under text box
        menuDiv.style.left	=XUtils.getAbsoluteLeft(textBox)+ "px";
        menuDiv.style.top	=XUtils.getAbsoluteTop(textBox) + textBox.offsetHeight + "px";
       
        menuDiv.style.visibility = 'hidden';    
   
        //Add event listeners to text box
        XUtils.addEventListener(textBox, "keydown",  self.onTextBoxKeyDown);
        XUtils.addEventListener(textBox, "keypress", self.onTextBoxKeyPress);
        XUtils.addEventListener(textBox, "keyup",    self.onTextBoxKeyUp);
        XUtils.addEventListener(textBox, "blur",     self.onTextBoxBlur);
                		
        //Disable autocomplete on textbox
        textBox.setAttribute("autocomplete", "off");
        
   		TRACE("AutoSuggestMenu.render Moving to menuDiv.style.left=" + menuDiv.style.left + ", " + menuDiv.style.top);
		
		_dom=menuDiv;
		
        
        if (XUtils.isIE() && self.useIFrame)
        {
            _iFrame=createIFrame(); //Use IFrame to overlap Select controls in IE
           
            if (_dom.style.zIndex==null)
                _dom.style.zIndex=0;
                
            _iFrame.style.zIndex=_dom.style.zIndex;
           
            document.body.appendChild(_iFrame);
           
            //Display menu in front of iframe
            _dom.style.zIndex=_dom.style.zIndex+1;
        }
         
        TRACE("AutoSuggestMenu.render  _dom.style.zIndex=" + _dom.style.zIndex);
		
        //Add menu to the page
        document.body.appendChild(_dom); 
    }
    
        

    //=================================================
    //Event handlers
    //=================================================
    
     
    //Called from AutoSuggestMenuItem when clicked
    self.onMenuItemClick = function(itemIndex)
	{
    	TRACE("AutoSuggestMenu.onMenuItemClick  itemIndex=" + itemIndex);
	
		selectMenuItem(itemIndex);
		self.hide();
		
		//onBlur was called when user clicked on item. So switch the focus back to TextBox
		focusOnTextBox();
	}


    //Called from AutoSuggestMenuItem when higlighted
	self.onMenuItemMouseOver = function(itemIndex)
	{
	    //TRACE("AutoSuggestMenu.onMenuItemMouseOver itemIndex=" + itemIndex);
		selectMenuItem(itemIndex, false);
	}
	
	
	
    //The rest of the events are called from textbox
	self.onTextBoxKeyDown = function(evt)
	{
		TRACE("AutoSuggestMenu.OnTextBoxKeyDown  " + XUtils.getEventKey(evt) + ", " + self.textBoxID);
		
		//Save current text box value before key press takes affect
		_oldTextBoxValue=getTextBoxValue();
		TRACE("AutoSuggestMenu.OnTextBoxKeyDown  old text box value='" + _oldTextBoxValue + "'");
	
		var key=XUtils.getEventKey(evt);
				
		TRACE("AutoSuggestMenu.OnTextBoxKeyDown  Key is " + key);
				
		//Detect if the user is using the down button
		if(key==38) //Up arrow
		{
			moveUp();
		}
		else if(key==40) //Down arrow
		{
			moveDown();
		}
		else if(key==13) //Enter
		{
			TRACE("AutoSuggestMenu.OnTextBoxKeyDown : isVisible - " + self.isVisible());
			if (self.isVisible())
			{
			    if (!self.updateTextBoxOnUpDown)
			        updateTextBoxValue();
			    
				self.hide();
				
				_cancelSubmit=true;
     		}
     		else
     		{
     			_cancelSubmit=false;
     		}
		}
						
		return true;
	}
	
		
	self.onTextBoxKeyPress = function(evt)
	{
		TRACE("AutoSuggestMenu.onTextBoxKeyPress : " + XUtils.getEventKey(evt));
		
		if ((XUtils.getEventKey(evt)==13) && (_cancelSubmit)) 
		{
		    if (!evt) 
                evt = window.event;
   
			evt.cancelBubble = true;
			evt.returnValue = false;
			
			if (evt.stopPropagation)   //For FireFox
		    {
		        evt.preventDefault();
		        evt.stopPropagation();
		    }
		}
			
		return true;
	}
		
	
	self.onTextBoxKeyUp = function(evt)
	{
		var key=XUtils.getEventKey(evt);
		
		TRACE("AutoSuggestMenu.onTextBoxKeyUp " + key);
		
		var newValue=getTextBoxValue();
			
		//Skip up/down/enter
		if ((key!=38) && (key!=40) && (key!=13))
		{
			//Limit num of characters to display suggestions	
			if ((newValue.length > 0) &&
			    (newValue.length >= self.minSuggestChars) &&
			    (newValue.length <= self.maxSuggestChars))
			{
			    //Set timer to update div.  If user types quickly return suggestions when he stops.  
              	var divMenu = _dom;
				if (_keyPressTimer!=null) 
				    window.clearTimeout(_keyPressTimer);
								
			    //Setup a callback function with timer
			    TRACE("AutoSuggestMenu.OnTextBoxKeyUp newValue=" + newValue + ", self.keyPressDelay=" + self.keyPressDelay);	
				_keyPressTimer = window.setTimeout(self.onTextBoxKeyUpTimer, self.keyPressDelay);
			}
			else
			{
			    //Hide the menu if it is visible
			    if (self.isVisible())
			        self.hide();
			}
		
		    TRACE("AutoSuggestMenu.onTextBoxKeyUp self.oldTextBoxValue=" + _oldTextBoxValue + ", newValue=" + newValue);
		
	    	if (_oldTextBoxValue!=newValue)
	    		self.setSelectedValue("");
		}
	}
				
	
	self.onTextBoxKeyUpTimer = function()
	{
	    TRACE("AutoSuggestMenu.onTextBoxKeyUpTimer");
	    refreshMenuItems();
	}
	
	
	self.onTextBoxBlur = function()
	{
		TRACE("AutoSuggestMenu.onTextBoxBlur");
	
	    //Hide menu with a slight delay - in case there was a click
	    if (_cancelOnBlur)
	        focusOnTextBox();
	    else
	        _onBlurTimer=window.setTimeout(self.hide, 500);
		
		_cancelOnBlur=false;
	}	
	
	
	self.onNextPage = function()
	{
	    TRACE("AutoSuggestMenu.onNextPage");
        focusOnTextBox();
	    
	    refreshMenuItems(REFRESH_TYPE_NEXT_PAGE);
	    
	    return false;
	}
	
	
	self.onPreviousPage = function()
	{
	    TRACE("AutoSuggestMenu.onNextPage");
	    focusOnTextBox();
	     
	    refreshMenuItems(REFRESH_TYPE_PREVIOUS_PAGE);
	    
	    return false;
	}
	
	
	self.onMenuScroll = function()
	{
	    TRACE("AutoSuggestMenu.onMenuScrol");
	    focusOnTextBox();
	   
	    _cancelOnBlur=true;
	}
}


//Static methods

AutoSuggestMenu.getMenu = function(menuID)
{
    var div=$(menuID);
    
    if (div==null)
        throw "AutoSuggestMenu (ID: '" + menuID + "') doesn't exist";
          
    var menu=div.sourceObject;
    return menu;
}



//Required for ASP.NET Ajax Extensions
if(typeof(Sys) !== "undefined")
    Sys.Application.notifyScriptLoaded();
