//------------------------------------------------------------------
//Author: Garry Harstad
//Created: Sept 20 2007
//Purpose: to provide reusable AJAX based JavaScript functions.
//------------------------------------------------------------------

//---------------------------------------------------------------------------------------------------------
//Function that loops fields in a form and creates a concatenated URL string out of the name value pairs.
//---------------------------------------------------------------------------------------------------------
function getFormData(form)
{
  var dataString = "";
  var elemArray = form.elements;

  //Private inner function
  function addParam(name, value)
  {
    //dataString += (dataString.length > 0 ? "&" : "") + escape(name).replace(/\+/g, "%2B") + "=" + escape(value ? value : "").replace(/\+/g, "%2B");
    dataString += (dataString.length > 0 ? "&" : "") + encodeURIComponent(name) + "=" + encodeURIComponent(value ? value : "");
  }

  for (var i = 0; i < elemArray.length; i++)
  {
    var element = elemArray[i];
    if (element.type) { var elemType = element.type.toUpperCase();}
    else { var elemType = undefined}  //fieldsets are a form element but have no type... skip over
    var elemName = element.name; //Only looks at NAME (not ID)
    if (elemName && elemType)
    {
      //alert("elemName="+elemName+"\nType="+elemType)
      if (elemType == "TEXT" || elemType == "TEXTAREA" || elemType == "PASSWORD" || elemType == "HIDDEN" || elemType == "EMAIL"  || elemType == "URL"  || elemType == "number") { addParam(elemName, element.value); }
      else if (elemType == "CHECKBOX" && element.checked) { addParam(elemName, element.value ? element.value : "On"); }
      else if (elemType == "RADIO" && element.checked) { addParam(elemName, element.value); }
      else if (elemType == "SUBMIT" && elemName && elemName!= '' && element.includeValue) { addParam(elemName, element.value); } //IF the submit button has a name, and has been marked by the calling script to be included, add value
      else if (elemType.indexOf("SELECT") != -1)
      {
        for (var j = 0; j < element.options.length; j++)
        {
          var option = element.options[j];
          if (option.selected) { addParam(elemName, option.value); }
        }
      }
    }
  }
  return dataString;
}


//---------------------------------------------------------------------------------------------------------
//Function that Submits, via AJAX, a form (post), or a URL string (get), and returns the data response.
//---------------------------------------------------------------------------------------------------------
var autoSaveDebug = true;
function submitFormData(form,fnRef,objRef,messageObj)
{ 
  var xhr;
  if (!messageObj){messageObj = new Object()} //the message object is any sort of object the calling function wants the receiving function to have.
  if (window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
  else if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); }
  
  else { return null; }
  if(typeof form == "string")
  {
    //It wasn't a form object. So, I assume that the passed in value is the URL (including querystring parameters as form data) and set the method to "GET" and the ACTION as the URL
    var method="GET"
    var url = form;
    var action=url
  }
  else
  {
    var method = form.method ? form.method.toUpperCase() : "GET";
    var action = form.action ? form.action : document.URL;
    var data = getFormData(form);
    //alert("DATA="+data)
    var url = action;
  }
  //alert("Method="+method+"\naction="+action+"\ndata="+data+"\nURL="+url)
  
  if (data && method == "GET")
  {
    //url += "?" + data; //Note: At a later date, check for existing querystring (?+) in the actual form action string before appending a "?", it may need a "&".
  }
  
  if(typeof fnRef == 'function') 
    {
      xhr.onreadystatechange = function()
        {
          if (xhr.readyState == 4 && xhr.status == 200)           
          {          
              if (typeof objRef == 'object') {fnRef.call(objRef,xhr.responseText, messageObj) }                    
              else {fnRef(xhr.responseText, messageObj)}          
          }
          else if (xhr.readyState == 4 && xhr.status == 500) {debugResponse(xhr.responseText)}
        }
    }
    
  xhr.open(method, url, true);
  
  /*
  function submitCallback()
  {
    if (xhr.readyState == 4 && xhr.status == 200)
    {
      //autoSaveDebug = false;
      //alert("Auto-Save Error: " + xhr.status + " " + xhr.statusText);
      //alert("WE ARE READY...\n\n"+xhr.responseText)
      //return xhr.responseText;
    }
  }
  */
  //xhr.onreadystatechange = function() { return xhr };
  //xhr.onreadystatechange = submitCallback;
    
  xhr.setRequestHeader("Ajax-Request", "Ajax-Save");
  if (method == "POST")
  {
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.send(data);
  }
  else { xhr.send(null); }
  return xhr;
}


//Function to get a products basic details and pop it into an Ajax pane.
function getProductBasics()
{
  //alert('this = ' + this + '\n' + 'this.pid = ' + this.pid)
  loadingajaxcontent();
  gethtml("/includes/ajax/product_moredetails_ajx.cfm?pid=" + this.pid + '&rnd=' + Math.random(), "ajaxcontainercontent");
  positionPalette(document.getElementById("ajaxcontainer"), this, 'right');
  //palElem is the palette element, sourceElement is the element that invoked it, dir is left or right, offset is an optional argument object containing x and y offsets
}


function loadingajaxcontent()
{
   document.getElementById("ajaxcontainercontent").innerHTML = "<center><img src='"+jsthemeiconpath+"ajax-loader.gif'><br>Fetching content...</center>";
   togglelayer("ajaxcontainer",1);
}



function getBodyDimensions()
  {
    var dims = {h:0,w:0, scrollX:0,scrollY:0}
    if (window.innerHeight) 
      {
        dims.h = window.innerHeight;
        dims.w = window.innerWidth;
      }
    else if (document.documentElement.clientHeight > 0) 
      {
        dims.h = document.documentElement.clientHeight
        dims.w = document.documentElement.clientWidth;
      }
    else 
      {
        dims.h = document.body.clientHeight
        dims.w = document.body.clientWidth  
      }
    
    //I'd like to assume these follow the same browser rules as above, but I'm not sure, so separate branch
    if (window.pageYOffset) 
      {
        dims.scrollX = window.pageXOffset 
        dims.scrollY = window.pageYOffset
      }
    else if (document.documentElement.scrollTop) 
      {
        dims.scrollX = document.documentElement.scrollLeft
        dims.scrollY = document.documentElement.scrollTop
      }
    else 
      {
        dims.scrollX = document.body.scrollLeft
        dims.scrollY = document.body.scrollTop
      }
  
    return dims
  
  } 

//positions a block element in the center of the page.  
function centerBlockElement(DOMnode)
  {
    var dims = getBodyDimensions();
    var boddy = document.getElementsByTagName('BODY')[0]
    if(!boddy) return
    boddy.appendChild(DOMnode)    
    var midWindow = {x:(dims.scrollX + (dims.w/2)), y:(dims.scrollY + (dims.h/2))}
    var finalLeft =  midWindow.x - (DOMnode.offsetWidth/2)
    var finalTop =   midWindow.y - (DOMnode.offsetHeight/2)
    
    DOMnode.style.display = 'block'
    DOMnode.style.position = 'absolute'
    DOMnode.style.left = finalLeft + 'px'
    DOMnode.style.top = finalTop + 'px'
  }


//function to position a palette relative to its activating element, left or right.
function positionPalette(palElem,sourceElem,dir,offset) //palElem is the palette element, sourceElement is the element that invoked it, dir is left or right, offset is an optional argument object containing x and y offsets
  { 
    if (!dir) {dir = 'right'} 
    if (!offset) {offset = {x : 0, y : 0}}
    else
      {
        if (!offset.x) {offset.x = 0}
        if (!offset.y) {offset.y = 0}
      }
      
    var dims = getBodyDimensions();
    var targetPos = getObjPosition(sourceElem) ;
    //alert(targetPos.x + ', ' + targetPos.y + '\n' + newtargetPos.x + ', ' + newtargetPos.y) 
    
    targetPos.x += offset.x;
    targetPos.y += offset.y;
    
    //alert(targetPos.x + ', ' + targetPos.y) 
    
    var paletteHeight = palElem.offsetHeight + 20;
    var paletteWidth = palElem.offsetWidth + 20;
    
    winHeight = dims.h;
    winWidth = dims.w
    scrollX = dims.scrollX
    scrollY = dims.scrollY
    
    
    //Vertical Positioning
    if ((targetPos.y + paletteHeight) < (winHeight + scrollY)) //palette will fit in window below link, position flush with link
    {
      var finalY = targetPos.y ; /*alert('case 1')*/
    }
    
    else if ((targetPos.y - paletteHeight) > scrollY) //palette will fit in  in window above link, position bottom flush with link
    {
      var finalY = targetPos.y - paletteHeight + 30; /*alert('case 2'); */      
    }
    
    else //palette is too big, center it in window
    {
       if (paletteHeight > winHeight) {var finalY = scrollY + 15;/* alert('Case 3a')*/} //too big to fit in window
        else //center vertically in window
        {
           var midWindow = scrollY + (winHeight/2)
           var finalY =   midWindow - (paletteHeight/2)
           /* alert('Case 3b')*/
        }
    }
    //horizontal Positioning
    if (dir == 'left') 
    {
      var finalX = targetPos.x - (paletteWidth + 30);
      if (finalX < scrollX) {finalX = targetPos.x + 45;}
    }
    else 
    {
      var finalX = targetPos.x + 35;
      if ((finalX + paletteWidth)> (scrollX + winWidth))
         {
          var temp = targetPos.x - (paletteWidth + 10);
          if (temp > 10) {finalX = temp} //if it will fit to left, put it there, if not, let it be
         }
    }
    palElem.style.left = finalX + 'px'
    palElem.style.top = finalY + 'px'
  }  


//-----------------------------------------------------------------------------------------------
//Function to place the contents of a response into the innerHTML of a container Object
//-----------------------------------------------------------------------------------------------
function gethtml(formorstring,containerid)
{
  //if(loaderhtml) { document.getElementById(containerid).innerHTML=loaderhtml; }
  //alert("Type of formorstring="+typeof formorstring+"\nformorstring="+formorstring+"\ncontainerid="+containerid)
  
  var ajaxResponse=submitFormData(formorstring);
  
  if(ajaxResponse.readyState == 4 && ajaxResponse.status == 200)
  {
    if(containerid) { pophtml(containerid,ajaxResponse.responseText); }
    //prevent memory leak as browser might not release instances depending on the XHTTP Request Method.
    ajaxResponse.abort();
    delete ajaxResponse; 
  }
  else
  { //alert("HERE!");
    ajaxResponse.onreadystatechange =
    function()
    {
      if (ajaxResponse.readyState == 4 && ajaxResponse.status == 200)
      {
        if(containerid) { pophtml(containerid,ajaxResponse.responseText); }
        //prevent memory leak as browser might not release instances depending on the XHTTP Request Method.
        ajaxResponse.abort();
        delete ajaxResponse;
      }
      else
      {
        //DEBUG ONLY: COMMENT OUT IN PRODUCTION!
        /*if(containerid) { pophtml(containerid,ajaxResponse.responseText); }
        debugResponse(ajaxResponse.responseText);*/
      }
      ;
    }
  }

}

function pophtml(containerid,content)
{
  document.getElementById(containerid).innerHTML=content;
}

function debugResponse(responseText)
{
   try
   {
   var tmp = window.open();
   tmp.document.open();
   tmp.document.write(responseText);
   tmp.document.close();
   }
   catch(e)
   {
      document.write(responseText);
   }
}

/* function to generate pagination controls for AJAX driven data fetch.
   argObj consists of the following properties:
     startRow = current start row
     endRow = current end row
     totalCount = how many total
     maxRows = how many rows per page
     containerIdRoot is a string containing the beginning part of the IDs of the containers for the pageination controls.
        Top and bottom containers should already exist on the page.
     callBackFN is a reference to a function called by each link on click to get the next chunk of data. 
        It will act on startRow/endrow properties that we'll set on the links. */
      
function createPagination(argObj)
  {
    var pageareas = ['Top','Bottom']; 
    var pContainerElem
    for (var a = 0; a < pageareas.length; a++)
      {
        pContainerElem = document.getElementById(argObj.containerIdRoot + pageareas[a])
        if(pContainerElem != null)
          {           
            destroyChildNodes(pContainerElem)//Clear out old contents 
            
            var widget = createElementAndText('',pContainerElem,'DIV') // overall container for the control
            var endpoint = (argObj.endRow < argObj.totalCount) ? argObj.endRow : argObj.totalCount    
            
            widget.className = 'paginatecontrol'
            var totalItems = createElementAndText('Showing Items ',widget,'DIV')
            
            //starting row
            createElementAndText(argObj.startRow,totalItems,'B')
            totalItems.appendChild(document.createTextNode(' - '))
            
            //end row
            createElementAndText(endpoint,totalItems,'B')
            totalItems.appendChild(document.createTextNode(' of '))
            
            //out of total
            createElementAndText(argObj.totalCount,totalItems,'B')            
            
            
            //create the links
            var linkDiv = createElementAndText('',widget,'DIV') // overall container for the control
            linkDiv.className = 'p_subcontrols'
            
            //create the 'First' link/span
            
            if (argObj.startRow == 1) //we're on the first page
              {
                var outerSpan = createElementAndText('',linkDiv,'SPAN')
                outerSpan.className = 'p_prev'
                var innerSpan = createElementAndText('< First ',linkDiv,'SPAN')
                innerSpan.className = 'disabled'      
              }
            else
              {
                var outerSpan = createElementAndText('< ',linkDiv,'SPAN')
                outerSpan.className = 'p_prev'
                isSecondPage = argObj.startRow == argObj.maxRows + 1
                
                var linktext = (isSecondPage) ? 'First': 'Previous';
                var firstStartRow = argObj.maxRows + 1
                makePageLink(linktext,firstStartRow,outerSpan,isSecondPage)
              }
              
            //create the number links
            var numberlinks = createElementAndText(' ',linkDiv,'SPAN')
            numberlinks.className = 'p_links'
            
            //generate pagination links
            var fullpages = Math.floor(argObj.totalCount/argObj.maxRows)
            var pagestart,pageend,pagenum,pageLk
            
            //create the actual pagination number links
            for (var page = 0; page < fullpages; page++)
              {
                pagenum = page + 1;
                pagestart = (page * argObj.maxRows) +1        
                makePageLink(pagenum,pagestart,numberlinks,(pagestart == argObj.startRow))
                numberlinks.appendChild(document.createTextNode(' | ')) //add a divider          
              }
              
             //add the final numeric link, if we've got one hanging.     
             if (fullpages * argObj.maxRows < argObj.totalCount)
              {
                pagenum++
                pagestart = (fullpages * argObj.maxRows) + 1        
                makePageLink(pagenum,pagestart,numberlinks,(pagestart == argObj.startRow))
                numberlinks.appendChild(document.createTextNode(' | ')) //add a divider 
              }
              
             //add the "Next" Link         
             if (endpoint == argObj.totalCount) 
              {
               var nextLk = createElementAndText('Final >',linkDiv,'SPAN')
               nextLk.className = 'disabled'
               }
             else
              {
                var nextSpan = createElementAndText('',linkDiv,'SPAN')
                var pagestart = argObj.startRow + argObj.maxRows;
                makePageLink('Next',pagestart,nextSpan,false)
                nextSpan.appendChild(document.createTextNode(' >'))        
              }            
          
          } //end, pContainerElem not null      
      }//end loop over areas    
    
    //private function to actually generate the link
    function makePageLink(linktext,startRow,parentElem,isCurrentPage)
      {
        if (isCurrentPage)
          {
            var paginLink = createElementAndText('',parentElem,'A')
            createElementAndText(linktext,paginLink,'B')
          }
        else
          {
            var paginLink = createElementAndText(linktext,parentElem,'A')
          }
        paginLink.className = 'paginate'
        paginLink.href = window.location;
        paginLink.startRow = startRow;
        paginLink.endRow = startRow + argObj.maxRows -1;
        paginLink.onclick = argObj.callBackFN;
        paginLink.onmouseover = setStatus
        paginLink.onmouseout = function(){window.status = ''}
      }
    
  }//end, createPagination

/* Little function for pagination mouseovers. Included here in case the page doesn't include pagination.js  */
function setStatus()
  {
    var linkText = getNodeText(this.firstChild);
    window.status = (parseInt(linkText) == linkText) ? 'Page ' + linkText : linkText + ' Page';
    return true;
  }

 //==== UTILITY FUNCTIONS ===========

//function to convert JSON response to objects
function evalJSON(responseText)
  {
    return eval('(' + responseText + ')');//convert JSON string to object
  }
  

/* utility function to create an element and text (or not) and 
   appends to its parent, returning the new element for further manipulation. */ 
function createElementAndText(elemText,parentElement,element)  
  {
    if (typeof(element) == 'undefined') {element = 'TD'}
    var newElemObj = document.createElement(element)
    if (elemText != '') {newElemObj.appendChild(document.createTextNode(elemText))} //only create text node if text is provided.  
    parentElement.appendChild(newElemObj)
    return newElemObj
  }
 
// sets attributes on an element, given an object of attributes and values 
function setElemAttributes(elem, attrObj)
  {
    for (attr in attrObj) {elem.setAttribute(attr,attrObj[attr])}
  }

//function to recursively destroy the child nodes of an element, to free memory  
function destroyChildNodes(DOMelem)
  {
    while(DOMelem.childNodes.length)
      {
        var myFirstChild = DOMelem.firstChild
        if (myFirstChild.childNodes.length == 0)
          {
            var doomed = DOMelem.removeChild(DOMelem.firstChild)
            doomed = null
          }
        else {destroyChildNodes(myFirstChild)}
      }
  }
 
 /* Onchange event handler for a country selector; 
    binds corresponding state selector to the response function and fetches state data. */
 function getStateDataFromCountry_AJAX()
  {
    var thiscountry = getSelectValue(this)
    var urlstring = '/includes/ajax/returnStates_ajx.cfm?rnd=' + Math.random() + '&country=' +  thiscountry
    
    if (this.haslabels) {urlstring +='&returnLocalNames=yes'}
    
    var stateBox = this.form.elements[this.name.replace(/country/,'stateid')]
    if (!stateBox) {return false}
    submitFormData(urlstring,updateStates_AJAX,stateBox)
    if (this.additionalFunction && typeof this.additionalFunction == 'function') {this.additionalFunction()} //hook for other things
  } 

/* Companion response function to getStateDataFromCountry_AJAX. 
   Called as a method of the intended state selector, and repopulates it with the result of the Ajax call. 
   If the selectbox has been given a includeStateCode property set to true, will include state codes in each option.
   If the selectbox has a postUpdateFunction method, it will be called upon completion. */
function updateStates_AJAX(responseText)
  {
     //debugResponse(responseText)
     response = evalJSON(responseText)
     if (!response.success) {alert('Sorry, unable to fetch state data'); return false}
     this.options.length = 0
     if(!response.stateArray.length)
       {
        this.options[0] = new Option('No states for this country','',true)
        this.disabled = true
       }
     else
      {
        this.disabled = false
        this.options[0] = new Option('Please select one','',true);
        var thisState,thisStateText
        for (var i = 0, statelen = response.stateArray.length; i < statelen; i++)
          {
            thisState = response.stateArray[i]
            thisStateText = this.includeStateCode ? thisState.statecode + ' -  ' + thisState.statename : thisState.statename
            this.options[this.options.length] = new Option(thisStateText,thisState.stateid)
          } //end, for         
      } //end, we have states 
    if(response.country)  
      {
        if (response.country.localRegionName)
          {
            var regionlabel = document.getElementById(this.id.replace(/stateid/,'statelabel'));
            if(regionlabel) {regionlabel.firstChild.nodeValue = response.country.localRegionName}
          }
          
        if (response.country.postalName)
          {
            var ziplabel = document.getElementById(this.id.replace(/stateid/,'postalcodelabel'));
            if(ziplabel) {ziplabel.firstChild.nodeValue = response.country.postalName}
          }
      }    
    if (this.postUpdateFunction && typeof this.postUpdateFunction == 'function') {this.postUpdateFunction()} //hook for other things
  }//end function  


