﻿/*  FORE Fracture Risk Calculator Main Page (Pro editions)
    Russ Walker, Walker Associates

    Common JavaScript for fracture risk calculator editions

    NOTE: Version numbering restarted when code factored out of original page; see
    comments in FRCPro.aspx for revisions prior to 3/3/2009.

    Version 1.0, 3/3/2009

    Developed by Walker Associates under contract to the Foundation for
    Osteoporosis and Education (FORE).

    Copyright (C) 2009 Foundation for Osteoporosis Research and Education.
    All rights reserved.


*/

    //page data
    var helpXML;    //XML documuent with field help messages
    var helpTimer;  //timer for display of field help after a time delay
    var mouseXPos;     //mouse position on mouseover for field help
    var mouseYPos;     //mouse position on mouseover for field help

    //cross-browser function to load an XML doc (from http://www.w3schools.com/dom/loadxmldoc.asp)
    function loadXMLDoc(dname)
    {
        var xmlDoc;
        // code for IE
        if (window.ActiveXObject)
        {
            xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
        }
        // code for Mozilla, Firefox, Opera, etc.
        else if (document.implementation && document.implementation.createDocument)
        {
            xmlDoc=document.implementation.createDocument("","",null);
        }
        else
        {
            showPageMessage("Sorry, your Web browser does not support all the functionss needed for this site. " +
                            "Some features of the site may be missing or may not work properly. " +
                            "We recommend using the latest version of Microsoft Internet Explorer, Netscape Navigator, or Firefox. " +
                            "Also, please make sure that JavaScript is enabled in your browser.");
        }
        xmlDoc.async=false;
        xmlDoc.load(dname);
        return(xmlDoc);
    }

    //initialize page
    function init(helpfile) {

        //load XML help messages
        helpXML = loadXMLDoc(helpfile);

        //put cursor into first field
        document.getElementById("txtAge").focus();

        //scroll to bottom of page if graph is displayed
        if (document.getElementById("riskchart")) {
            scrollToBottom();
        }
    }


    //display help for input field
    // this function just sets a timer to call the showDelayedHelp() function after a short delay
    function showFieldHelp(e,tgt, messageId) {
        tgt.style.backgroundColor = "lightblue";   // highlight help box
        mouseXPos = mouseX(e);                  // save mouse position
        mouseYPos = mouseY(e);
        helpTimer = setTimeout("showDelayedHelp('" + messageId + "')", 1000); //1 second delay
    }

    //show help for input field after a time delay
    //this function is called by a timeout set by the showFieldHelp() function
    function showDelayedHelp(messageId) {
        var messageDiv = document.getElementById("fieldhelp");    //get the message display div
        var positionDiv = document.getElementById(messageId);   //get the div to position on top of
        var messages=helpXML.getElementsByTagName("message");   //get array of all help messages from XML doc
        var i;
        var messageText = "Sorry, no help available";           //default help message
        for (i=0; i<messages.length; i++) {                     //search for message with this ID in the XML help doc
            if (messages[i].getAttribute("id") == messageId) {
                messageText = messages[i].childNodes[0].nodeValue;
                break;
            }
        }
        if (typeof document.body.style.maxHeight != "undefined") {
  		    // IE 7, mozilla, safari, opera 9
  		    // get position from the error message div for the field
		    var messagePosition = findPos(positionDiv); //get position for this message [left, top]
            messageDiv.style.left = messagePosition[0] + 'px';       //move to location and display
            messageDiv.style.top = messagePosition[1] + 'px' ;
		} else {
  		    // IE6, older browsers
  		    // can't rely on positioning so use mouse position
            messageDiv.style.left = mouseXPos + 'px';       //move to location and display
            messageDiv.style.top = mouseYPos + 'px' ;
	    }
        messageDiv.style.display = "block";
        messageDiv.innerHTML = messageText;

    }

    //hide help for input field
    function hideFieldHelp(tgt, messageId) {
        clearTimeout(helpTimer);        // cancel any pending help display if user moved the cursor away quickly
        var messageDiv = document.getElementById("fieldhelp");    //get the div for displaying the message
        messageDiv.innerHTML = "";
        messageDiv.style.display = "none";
        tgt.style.backgroundColor = "";   // un-highlight help box

    }

    //validate form inputs client-side
    function validate() {

        // get input controls to validate
        var txtAge = document.getElementById("txtAge");
        var txtHeight = document.getElementById("txtHeight");
        var txtHeightFeet, txtHeightInches;
        if (!txtHeight) {
            txtHeightFeet = document.getElementById("txtHeightFeet");
            txtHeightInches = document.getElementById("txtHeightInches");

        }
        var txtWeight = document.getElementById("txtWeight");
        var txtHipTScore = document.getElementById("txtHipTScore");
        var lstEthnicity = document.getElementById("lstEthnicity");

        // get message divs
        var divAgeMessage = document.getElementById("ageMessage");
        var divHeightMessage = document.getElementById("heightMessage");
        var divWeightMessage = document.getElementById("weightMessage");
        var divHipTScoreMessage = document.getElementById("hipTScoreMessage");
        var divEthnicityMessage = document.getElementById("ethnicityMessage");

        clearFieldMessages();       //clear any previous field messages

        if (validAge(txtAge, divAgeMessage) &&
            validHeight(txtHeight, txtHeightFeet, txtHeightInches, divHeightMessage) &&
            validWeight(txtWeight, divWeightMessage) &&
            validTScore(txtHipTScore, divHipTScoreMessage) &&
            validEthnicity(lstEthnicity, divEthnicityMessage) ) {

            return true;        //all valid

        } else {

            return false;       //validation error

        }

    }

    //display general page message
    function showPageMessage(messageText) {
        var messageDiv = document.getElementById("pagemessage");
        messageDiv.innerHTML = messageText;
        messageDiv.style.display = "block";
    }

    //clear field messages
    function clearFieldMessages() {
        //find and clear all divs in class fieldmessage
        var divs = document.getElementsByTagName("div");
        var i;
        for (i=0; i<divs.length; i++) {
            if (divs[i].className=="fieldmessage") {
                divs[i].innerHTML = "";
            }
        }
    }

// validate age entry

 function validAge(txtAge, divMsg) {

    var minAge = 45;    // minimum age
    var maxAge = 85;    // maximum age
    var valid;

    if (trim(txtAge.value).length == 0) {
        divMsg.innerHTML = "You must enter a number for age.";
        divMsg.style.color = "#FF0000";
        valid = false;
    } else if (isNaN(txtAge.value)) {
        divMsg.innerHTML = "Age must be a whole number.";
        divMsg.style.color = "#FF0000";
        valid = false;
    } else {

        // it's a number, convert it and check range
        var value = parseInt(txtAge.value);
        if (value < minAge || value > maxAge) {
            divMsg.innerHTML = "Age must be a whole number between " + minAge +
                                " and " + maxAge + ".";
            divMsg.style.color = "#FF0000";
            valid = false;
        } else {
            // entry valid
            divMsg.innerHTML = "";
            valid = true;
        }
    }

    if (!valid) {
        txtAge.focus();
    }
    return valid;


 }

 // validate height entry

 function validHeight(txtHeight, txtHeightFeet, txtHeightInches, divMsg) {


    var minHeight = 54;    // minimum height
    var maxHeight = 76;    // maximum height

    // min, max height in feet and inches so info in error messages will correspond
    // to how it's entered for interfaces that accept separate feet, inches values
    var minHeightFeet = Math.floor(minHeight/12);
    var minHeightInches = minHeight%12;
    var maxHeightFeet = Math.floor(maxHeight/12);
    var maxHeightInches = maxHeight%12;

    var valid = false;
    var feet, inches, value;


    if (txtHeight) {

        //check height in inches as a single field
        if (trim(txtHeight.value).length == 0) {
            divMsg.innerHTML = "You must enter the patient's height.";
            divMsg.style.color = "#FF0000";
            valid = false;
        } else if (isNaN(txtHeight.value)) {
            divMsg.innerHTML = "Height must be a whole number.";
            divMsg.style.color = "#FF0000";
            valid = false;
        } else {

            // it's a number, convert it and check range
            value = parseInt(txtHeight.value);
            if (value < minHeight || value > maxHeight) {
                divMsg.innerHTML = "Height must be between " + minHeight +
                                    " and " + maxHeight + " inches.";
                divMsg.style.color = "#FF0000";
                valid = false;
            } else {
                // entry valid
                divMsg.innerHTML = "";
                valid = true;
            }
        }
        if (!valid) {
            txtHeight.focus();
        }


    } else {

        //check separate feet, inches entries for height
        if (trim(txtHeightFeet.value).length == 0) {
            divMsg.innerHTML = "You must enter your height in feet and inches.";
            divMsg.style.color = "#FF0000";
            txtHeightFeet.focus();
            valid = false;
        } else if (isNaN(txtHeightFeet.value) || (trim(txtHeightInches.value).length > 0 && isNaN(txtHeightInches.value))) {
            divMsg.innerHTML = "Height must be entered as two whole numbers, feet and inches.";
            divMsg.style.color = "#FF0000";
            txtHeightFeet.focus();
            valid = false;
        } else {
            // have feet alone or feet + inches, convert it and check range
            feet = parseInt(txtHeightFeet.value);
            if (trim(txtHeightInches.value).length > 0) {
                inches = parseInt(txtHeightInches.value);
            } else {
                inches = 0;
            }
            value = feet*12 + inches;
            if (inches < 0 || inches > 11) {
                divMsg.innerHTML = "Inches must be a whole number between 0 and 11";
                divMsg.style.color = "#FF0000";
                txtHeightInches.focus();
                valid = false;
            } else if (value < minHeight || value > maxHeight) {
                divMsg.innerHTML = "Height must be a whole number between " + minHeightFeet +
                                    " feet " + minHeightInches + " inches " +
                                    " and " + maxHeightFeet + " feet " + maxHeightInches + " inches.";
                divMsg.style.color = "#FF0000";
                txtHeightFeet.focus();
                valid = false;
            } else {
                // entry valid
                divMsg.innerHTML = "";
                valid = true;
            }
        }

    }


    return valid;

 }

 // validate weight entry

 function validWeight(txtWeight, divMsg) {
    var minWeight = 70;    // minimum weight
    var maxWeight = 300;    // maximum weight
    var valid;

    if (trim(txtWeight.value).length == 0) {
        divMsg.innerHTML = "You must enter a number for weight.";
        divMsg.style.color = "#FF0000";
        valid = false;
    } else if (isNaN(txtWeight.value)) {
        divMsg.innerHTML = "Weight must be a whole number.";
        divMsg.style.color = "#FF0000";
        valid = false;
    } else {

        // it's a number, convert it and check range
        var value = parseInt(txtWeight.value);
        if (value < minWeight || value > maxWeight) {
            divMsg.innerHTML = "Weight must be a whole number between " + minWeight +
                                " and " + maxWeight + ".";
            divMsg.style.color = "#FF0000";
            valid = false;
        } else {
            // entry valid
            divMsg.innerHTML = "";
            valid = true;
        }
    }

    if (!valid) {
        txtWeight.focus();
    }
    return valid;
 }

 // validate T-score

 function validTScore(txtTScore, divMsg) {
    var minTScore = -3.9;    // minimum t-score
    var maxTScore = 10;    // maximum t-score
    var valid;

    if (!txtTScore) {
        //TScore field does not exist on this interface, so skip checking
        valid = true;
    } else if (trim(txtTScore.value).length == 0) {
        if (confirm("You did not enter the patient's T-score." +
                    "If you do not know the patient's T-score, click OK to use " +
                    "the average T-score for the patient's age. If you know the " +
                    "patient's T-score, click Cancel, enter the T-score, and retry " +
                    "the calculation.")) {
            //user confirmed, proceed without T-score (server-side code will assume
            //a Z-score of 0
                divMsg.innerHTML = "";
                valid = true;
        } else {
            //user cancelled, prompt for T-score entry
            divMsg.innerHTML = "Please enter the patient's t-score.";
            divMsg.style.color = "#FF0000";
             valid = false;
        }
    } else if (isNaN(txtTScore.value)) {
        divMsg.innerHTML = "T-score must be a number.";
        divMsg.style.color = "#FF0000";
        valid = false;
    } else {

        // it's a number, convert it and check range
        var value = parseFloat(txtTScore.value);
        if (value < minTScore || value > maxTScore) {
            divMsg.innerHTML = "T-score must be a number between " + minTScore +
                                " and " + maxTScore + ".";
            if (value < minTScore) {
                divMsg.innerHTML += " This model's predictions are inaccurate when BMD T-score is -4 or lower."
            }
            divMsg.style.color = "#FF0000";
            valid = false;
        } else if (value > 0) {
            if (confirm("You entered a t-score greater than zero. " +
                        "If this is correct, click OK.  If this is not correct, click Cancel.")) {
                // user confirmed, so proceed
                divMsg.innerHTML = "";
                valid = true;
            } else {
                // user cancelled
                divMsg.innerHTML = "Re-enter the t-score, using the minus sign for a negative number.";
                divMsg.style.color = "#FF0000";
                valid = false;
            }
        } else {
            // entry valid
            divMsg.innerHTML = "";
            valid = true;
        }
    }

    if (!valid) {
        txtTScore.focus();
    }
    return valid;
 }

// validate ethnicity

function validEthnicity(lstEthnicity, divMsg) {

    if (lstEthnicity.options[lstEthnicity.selectedIndex].text == "--Please select--") {
        divMsg.innerHTML = "You must select a race or ethnicity."
        divMsg.style.color = "#FF0000";
        valid = false;
    } else {
        valid = true;
    }

    if (!valid) {
        lstEthnicity.focus();
    }

    return valid;


}


// trim spaces from string

function trim(inputString) {
   // Removes leading and trailing spaces from the passed string. Also removes
   // consecutive spaces and replaces it with one space. If something besides
   // a string is passed in (null, custom object, etc.) then return the input.
   if (typeof inputString != "string") { return inputString; }
   var retValue = inputString;
   var ch = retValue.substring(0, 1);
   while (ch == " ") { // Check for spaces at the beginning of the string
      retValue = retValue.substring(1, retValue.length);
      ch = retValue.substring(0, 1);
   }
   ch = retValue.substring(retValue.length-1, retValue.length);
   while (ch == " ") { // Check for spaces at the end of the string
      retValue = retValue.substring(0, retValue.length-1);
      ch = retValue.substring(retValue.length-1, retValue.length);
   }
   while (retValue.indexOf("  ") != -1) { // Note that there are two spaces in the string - look for multiple spaces within the string
      retValue = retValue.substring(0, retValue.indexOf("  ")) + retValue.substring(retValue.indexOf("  ")+1, retValue.length); // Again, there are two spaces in each of the strings
   }
   return retValue; // Return the trimmed string back to the user
} // Ends the "trim" function

// reset input form

function resetForm() {

    //NOTE: Input fields will be cleared on server side;
    // this client-side function just takes care of the
    // UI aspects that are managed on the client side only

    clearFieldMessages();       //clear help/error messages for input fields

    return true;

}

// determine mouse X-position in document from mouse event (from http://javascript.about.com/library/blmousepos.htm)

function mouseX(evt) {
if (evt.pageX) return evt.pageX;
else if (evt.clientX)
   return evt.clientX + (document.documentElement.scrollLeft ?
   document.documentElement.scrollLeft :
   document.body.scrollLeft);
else return null;
}

// determine mouse Y-position in document from mouse event (from http://javascript.about.com/library/blmousepos.htm)
function mouseY(evt) {
if (evt.pageY) return evt.pageY;
else if (evt.clientY)
   return evt.clientY + (document.documentElement.scrollTop ?
   document.documentElement.scrollTop :
   document.body.scrollTop);
else return null;
}

//determine absolute position of an object relative to the entire document
// returns an array with [0] = left and [1] = top
// from http://www.quirksmode.org/js/findpos.html

function findPos(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
	}
	return [curleft,curtop];
}

//scroll to bottom of page

function scrollToBottom() {
    window.location="#after_results";
}

// highlight input field when it has the focus
function highlightField(field, hasFocus) {
    if (hasFocus) {
        field.style.backgroundColor = "lightblue";
    } else {
        field.style.backgroundColor = "white";
    }
}



