﻿// Schedulicity Common //
var Common = {};

Common.Environments = { Development: 1, Production: 2 };
Common.CurrentEnvironment = null;

Common.preProcessDefaultButton = function ()
{
    if (Common.IsTouchBrowser)
    {
        $$('input[type=text]').each(function (input)
        {
            input.blur();
        });

        alert('"Go" button is not supported. Please submit using the form buttons and links on the web page.');
    }
}

Common.applySeeMore = function (container, textElement, seeMoreLink, height, extras)
{
    window.addEvent('domready', function ()
    {
        container = $(container);
        textElement = $(textElement);
        seeMoreLink = $(seeMoreLink);

        if (!container)
            return;

        if (typeof height === 'number')
            height += 'px';

        // Create a copy of the valid-for box and expand its height.
        var tempDiv = container.clone();
        tempDiv.setStyle('height', 'auto');
        document.body.appendChild(tempDiv);
        var tempHeight = tempDiv.getHeight();
        tempDiv.setStyle('display', 'none');

        // Show or hide the see more link.
        if (tempHeight > textElement.getHeight())
        {
            seeMoreLink.setStyle('display', '');
        }
        else
        {
            seeMoreLink.setStyle('display', 'none');
            container.setStyle('height', 'auto');
        }

        var linkHeight = height;
        var tweenExtras = extras;

        seeMoreLink.addEvent('click', function ()
        {
            if (seeMoreLink.innerHTML === 'See more...')
            {
                // Determine the fully expanded height.
                var tempDiv = container.clone().inject(container, 'after');
                tempDiv.setStyle('height', 'auto');

                var height = tempDiv.getHeight() - 15;

                if (tweenExtras && tweenExtras.yOffset)
                    height += tweenExtras.yOffset;

                tempDiv.setStyle('display', 'none');

                tempDiv.destroy();

                seeMoreLink.innerHTML = 'Show less';
                container.tween('height', height);
            }
            else
            {
                seeMoreLink.innerHTML = 'See more...';
                container.tween('height', linkHeight);
            }
        });

    });
}

// For converting C# dynamic objects to a javascript-friendly hash
Common.convertDynamicToHash = function (dynamic)
{
    var hash = {};

    for (var i = 0, len = dynamic.length; i < len; i++)
    {
        hash[dynamic[i].Key] = dynamic[i].Value;
    }

    return hash;
}

Common.getQuerystring = function(key)
{
    key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");

    var regex = new RegExp("[\\?&]" + key + "=([^&#]*)");

    var qs = regex.exec(window.location.href);
    
    if (qs == null)
        return '';
    else
        return qs[1];
}


Common.addAnalyticsWithRedirect = function (category, action, optionalLabel, optionalValue, suppressWoopra, redirect)
{
    Common.addAnalytics(category, action, optionalLabel, optionalValue, suppressWoopra);

    location.href = redirect;
}

Common.addAnalytics = function (category, action, optionalLabel, optionalValue, suppressWoopra)
{
//    if (Common.CurrentEnvironment == Common.Environments.Development)
//    {
//        //alert('Adding analytics for: ' + category + " | " + action + " | " + optionalLabel + " | " + optionalValue);
//        return;
    //    }
    var keyValue = '';

    if (optionalLabel != null && optionalValue != null)
        keyValue = optionalLabel + ': ' + optionalValue;
    else if (optionalLabel != null)
        keyValue = optionalLabel;
    else
        keyValue = optionalValue;

    // GA - appears to be a synchronous call
    //    if (typeof pageTracker != 'undefined' && typeof pageTracker._trackEvent != 'undefined')
    //    {
    //        pageTracker._trackEvent(category, action, keyValue);
    //    }

    // GA - using async library
    if (typeof _gaq != 'undefined')
    {
        _gaq.push(['_trackEvent', category, action, keyValue]);
    }

    if (!suppressWoopra)
    {
        // WA
        if (typeof WoopraEvent != 'undefined')
        {
            var eventInfo = category + ' ' + action + ' - ' + keyValue;

            var myEvent = new WoopraEvent(eventInfo); // Initialize event and give it a name
            if (optionalLabel && optionalValue)
                myEvent.addProperty(optionalLabel, optionalValue);

            myEvent.fire();
        }
    }
}

Common.addHomepageVideoAnalytics = function(directory, page, videoTitle)
{
    var label = directory;
    if (page)
        label += '.' + page;

    Common.addAnalytics('Video', 'View', label, videoTitle, false);
}

Common.assertCurrentCrediential = function ()
{
    if (window.isSigningOut) return;

    var schedi = Cookie.read('.schedi');
    var compare = window.currentCredentialComparisonKey; // Empty string defines a user as not logged in

    if (compare !== '' && schedi !== compare)
    {
        $clear(window.currentCredentialTimer);
        try
        {
            CredentialPopup.show(); // Defined in App.Master as a LightBoxPopup
        } catch (e) { }
    }
}

Common.disableEnterKey = function (e)
{
    var key;
    if (window.event)
        key = window.event.keyCode; //IE
    else
        key = e.which; //firefox

    return (key != 13);
}

Common.findPos = function(obj)
{
    var curleft = curtop = 0;

    if (obj.offsetParent)
    {
        curleft = obj.offsetLeft - obj.scrollLeft + document.documentElement.scrollLeft;
        curtop = obj.offsetTop - obj.scrollTop + document.documentElement.scrollTop;
        while (obj = obj.offsetParent)
        {
            curleft += obj.offsetLeft - obj.scrollLeft;
            curtop += obj.offsetTop - obj.scrollTop;
        }
    }

    if (!window.event)
    {
        curleft -= document.documentElement.scrollLeft;
        curtop -= document.documentElement.scrollTop;
    }

    return [curleft, curtop];
}

Common.CloneObject = function(sourceObject)
{
    if (typeof (sourceObject) != 'object')
        return sourceObject;

    if (sourceObject == null)
        return null;

    var newObject = new Object();

    for (var i in sourceObject)
        newObject[i] = Common.CloneObject(sourceObject[i]);

    return newObject;
}


Common.showHelpfulTip = function (whereElement, sourceElementId, optionalXOffset, optionalYOffset)
{
    var xOffset = optionalXOffset || 5;
    var yOffset = optionalYOffset || 15;

    var pos = Common.findPos(whereElement);

    var x = pos[0], y = pos[1];

    x += xOffset;
    y += yOffset;

    if (document.documentElement.scrollTop)
        y -= document.documentElement.scrollTop;

    var element = $get(sourceElementId);
    element.style.display = 'none';
    element.style.position = 'absolute';
    element.style.left = x + 'px';
    element.style.top = y + 'px';

    document.body.appendChild(element);

    element.style.display = '';
}

Common.hideHelpfulTip = function(sourceElementId)
{
    $get(sourceElementId).style.display = 'none';
}

Common.globalFindElementPosition = function(obj, childAlign)
{
    var curleft = curtop = 0;

    if (typeof childAlign != 'undefined')
    {
        if (obj.tagName == 'TD' && obj.firstChild.nodeType == 1)
            obj = obj.firstChild;

        curleft = obj.offsetLeft - obj.scrollLeft + (document.documentElement.scrollLeft || document.body.scrollLeft);
        curtop = obj.offsetTop - obj.scrollTop + (document.documentElement.scrollTop || document.body.scrollTop);

        if (childAlign == 'right')
            curleft += obj.offsetWidth;

        while (obj = obj.offsetParent)
        {
            curleft += obj.offsetLeft - obj.scrollLeft;
            curtop += obj.offsetTop - obj.scrollTop;
        }
    }
    else if (obj.offsetParent)
    {
        curleft = obj.offsetLeft - obj.scrollLeft + (document.documentElement.scrollLeft || document.body.scrollLeft);
        curtop = obj.offsetTop - obj.scrollTop + (document.documentElement.scrollTop || document.body.scrollTop);
        while (obj = obj.offsetParent)
        {
            curleft += obj.offsetLeft - obj.scrollLeft;
            curtop += obj.offsetTop - obj.scrollTop;
        }
    }

    if (Sys.Browser.agent == Sys.Browser.Firefox || (Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version >= 8))
    {
        curleft -= (document.documentElement.scrollLeft || document.body.scrollLeft);
        curtop -= (document.documentElement.scrollTop || document.body.scrollTop);
    }
    else if (Sys.Browser.agent == Sys.Browser.Safari)
    {
        curtop -= document.body.scrollTop;
    }

    return [curleft, curtop];
}

Common.moveElementToPosition = function(element, leftCoordinate, topCoordinate)
{
    if (document.all)
    {
        var viewHeight = screen.height; //.body.clientHeight;
        var viewWidth = screen.width;   //document.body.clientWidth;
    }
    else
    {
        var viewHeight = window.innerHeight;
        var viewWidth = window.innerWidth;
    }

    var centerWidth = (viewWidth / 2);
    var centerHeight = (viewHeight / 2);

    var leftOffset = 40;
    var topOffset = 0;

    if (Sys.Browser.agent === Sys.Browser.InternetExplorer)
    {
        leftOffset = 25;
        topOffset = 30;
    }

    if (Sys.Browser.agent === Sys.Browser.Safari)
    {
        leftOffset = 34;
        topOffset = 30 - document.body.scrollTop;
    }

    if (Sys.Browser.agent === Sys.Browser.Firefox)
    {
        leftOffset = 30;
        topOffset = 30;
    }

    element.style.top = (topCoordinate - topOffset) + "px";
    element.style.left = (leftCoordinate + leftOffset) + "px";
}

Common.clearDropDownList = function (dropDownList)
{
    // Clear options (and option groups)
    while (dropDownList.hasChildNodes())
    {
        dropDownList.removeChild(dropDownList.firstChild);
    }
}

// Creates a new option at the top of a drop-down list and selects it
Common.injectNewTopDropDownListValue = function(dropDownElement, text, value)
{
    var option = document.createElement('option');
    option.value = value;
    option.appendChild(document.createTextNode(text));

    dropDownElement.insertBefore(option, dropDownElement.firstChild);

    dropDownElement.selectedIndex = 0;
}

// Sets the selected option for an HTML DropDownList
Common.setDropDownListValue = function(dropDownElement, value)
{
    var success = false;

    for (var i = 0, len = dropDownElement.options.length; i < len; i++)
    {
        if (dropDownElement.options[i].value == value)
        {
            dropDownElement.selectedIndex = i;

            success = true;

            break;
        }
    }

    return success;
}

// Gets the text associated with a value in the drop down
Common.getDropDownListItemText = function (dropDownElement, value)
{
    for (var i = 0, len = dropDownElement.options.length; i < len; i++)
    {
        if (dropDownElement.options[i].value == value)
        {
            return dropDownElement.options[i].text;
        }
    }

    return '';
}

Common.getRadioButtonListValue = function (radioButtonListName)
{
    var radioButtonList = document.forms[0][radioButtonListName];
    var radioOptionSelected = null;

    // if single option and not checked
    if (isNaN(radioButtonList.length) && radioButtonList.checked)
    {
        radioOptionSelected = radioButtonList;
    }
    else
    { // multi options
        for (var i = 0, len = radioButtonList.length; i < len; i++)
        {
            if (radioButtonList[i].checked)
            {
                radioOptionSelected = radioButtonList[i];
                break;
            }
        }
    }

    return radioOptionSelected.value;
}

Common.getRadioButtonListText = function (radioButtonListName)
{
    var radioButtonList = document.forms[0][radioButtonListName];
    var radioOptionSelected = null;

    // if single option and not checked
    if (isNaN(radioButtonList.length) && radioButtonList.checked)
    {
        radioOptionSelected = radioButtonList;
    }
    else
    { // multi options
        for (var i = 0, len = radioButtonList.length; i < len; i++)
        {
            if (radioButtonList[i].checked)
            {
                radioOptionSelected = radioButtonList[i];
                break;
            }
        }
    }

    return $(radioOptionSelected).getParent().getElement('label[for=' + radioOptionSelected.id + ']').getProperty('text');
}

Common.resetRadioButtonList = function (radioButtonListName)
{
    var radioButtonList = document.forms[0][radioButtonListName];
    var radioOptionSelected = null;

    // if single option and not checked
    if (isNaN(radioButtonList.length) && radioButtonList.checked)
    {
        radioOptionSelected = radioButtonList;
    }
    else
    { // multi options
        radioOptionSelected = radioButtonList[0];

        for (var i = 0, len = radioButtonList.length; i < len; i++)
        {
            if (radioButtonList[i].checked)
            {
                radioButtonList[i].checked = false;
            }
        }
    }

    radioOptionSelected.checked = true;
}

Common.createNotAvailableElement = function(adjacentElement, hide, supportType)
{
    adjacentElement = $(adjacentElement);

    var element = new Element('div');
    var supportLabel = '';

    if (supportType == 'fileupload')
        supportLabel = 'File upload support is not available on your device';

    element.innerHTML = '<span style="color: Red; font-size: 13px; font-weight: normal">' + supportLabel + '</span>';

    //adjacentElement.inject(element, 'after');
    element.inject(adjacentElement, 'after');

    if (hide)
        adjacentElement.setStyle('display', 'none');


}

Common.createNotAvailableForFileUpload = function()
{
    var fileUploads = $$('input[type="file"]');

    for (var i = 0, len = fileUploads.length; i < len; i++)
    {
        Common.createNotAvailableElement(fileUploads[i], true, 'fileupload', null);
    }
}

// MOOTOOLS EXTN
Common.pulseFade = function(element)
{
    var pf = new PulseFade(element, {
        min: .34,
        max: 1,
        duration: 600,
        times: 3,
        onComplete: function()
        {
            Calendar.hoverTarget.style.visibility = 'visible';
        },
        onStart: function()
        {
            Calendar.hoverTarget.style.visibility = 'hidden';
        }
    });

    pf.start();
}

Common.collapseDestroy = function (element, callback)
{
    element = $(element);

    var onComplete = function ()
    {
        element.destroy();

        if (callback)
            callback();
    };

    var fx = new Fx.Morph(element, { duration: 400, wait: false }).addEvent('onComplete', onComplete);
    fx.start({ 'height': 0, 'opacity': 0 });
}

Common.minifyTimeString = function (timeString, lowerCase)
{
    if (!lowerCase)
        return timeString.replace(/:00/, '').trim();
    else
        return timeString.replace(/:00/, '').trim().toLowerCase();
}

Common.getTimeAbbreviation = function(minutes)
{
    var timeString = '';
    var hours = parseInt(minutes / 60);

    if (minutes < 60)
        timeString = minutes + ' min';
    else
    {
        var remainderMinutes = minutes % 60;

        if (remainderMinutes != 0)
            timeString = hours + ' hr ' + remainderMinutes + ' min';
        else
        {
            timeString = '' + hours + ' hour';

            if (hours > 1)
                timeString += 's';
        }
    }

    return timeString;
}

Common.getScrollTop = function ()
{
    if (typeof pageYOffset != 'undefined')
    {
        //most browsers
        return pageYOffset;
    }
    else
    {
        var B = document.body; //IE 'quirks'
        var D = document.documentElement; //IE with doctype
        D = (D.clientHeight) ? D : B;
        return D.scrollTop;
    }
}

Common.FormatMaxTextLength = function(text, maxLength)
{
    if( text.length <= maxLength )
        return text;

    return text.substr(0, maxLength) + '...';
}

Common.watermarkTextBox = function (element, watermarkText, clearOnFocus)
{
    element = $(element);

    element.setProperty('watermarkText', watermarkText);

    // initialize value / properties
    //if (element.value == '' && !element.hasClass('watermarkTextItalic'))
    {
        element.value = watermarkText;
        element.addClass('watermarkTextItalic');

        try
        {
            //element.setCaretPosition(0);
        }
        catch (e) { }
    }

    if (clearOnFocus)
    {
        element.addEvent('focus', function (event)
        {
            if (this.value == this.getProperty('watermarkText'))
            {
                this.value = '';
                this.removeClass('watermarkTextItalic');
            }
        });
    }

    element.addEvents({
        'mousedown': function (event)
        {
            if (this.value == this.getProperty('watermarkText'))
            {
                this.setCaretPosition(0);

                event.stopPropagation();

                return false;
            }
        },
        'mouseup': function (event)
        {
            if (this.value == this.getProperty('watermarkText'))
                this.setCaretPosition(0);
        },
        'blur': function (event)
        {
            if (this.value == '')
            {
                if (!this.hasClass('watermarkTextItalic'))
                    this.addClass('watermarkTextItalic');

                this.value = this.getProperty('watermarkText');
            }
        },
        'keydown': function (event)
        {
            if (this.value == this.getProperty('watermarkText'))
            {
                this.value = '';
                this.removeClass('watermarkTextItalic');
            }
        },
        'keyup': function (event)
        {
            if (this.value == '')
            {
                if (!this.hasClass('watermarkTextItalic'))
                    this.addClass('watermarkTextItalic');

                this.value = this.getProperty('watermarkText');
                this.setCaretPosition(0);
            }
        }
    });

    // mootools version isn't working correctly.
    element.onpaste = function (event)
    {
        if (element.value == element.getProperty('watermarkText'))
        {
            element.value = '';
            element.removeClass('watermarkTextItalic');
        }
    };
}

// Simple instance class for the sole InfoBar
// Example usage: InfoBar.addOption('X has been updated.', [{ text: 'undo', method: function() { alert('implement undo method'); } }]);
var InfoBar = {};

// Adds an option (line) to the info bar with optional links defined as such [ { text: 'link text', method: fn, }, ... ]
// Sticky will permanently hold the bar in place until dismissed
// noClearBr removes the final <br> tag in the message for positioning purposes.
InfoBar.addOption = function (message, uniqueID, options, sticky, closable, noClearBr)
{
    this.initialize();

    if (this.destroyRowByUniqueID(uniqueID)) // assure a previous undo row with this id does not exist. useful for multiple operations
    {
        this.resetTimer(true);
    }

    while (this.currentMessages.length >= this.maxMessages)
    {
        this.destroyRowByRowElement(this.currentMessages[0].element);
    }

    var row = this.rowTemplate.clone();
    var messageElement = row.getElement('.infoBarMessage');

    if (noClearBr === true) 
        messageElement.setProperty('html', message);
    else
        messageElement.setProperty('html', message + '<br clear="all" />');

    for (var i = 0; i < options.length; i++)
    {
        var option = options[i];
        var optionIndex = i + 1;

        var text = option.text;
        var method = option.method;

        var optionElementWrapper = row.getElement('.infoBarOption' + optionIndex + 'Wrapper');
        var optionElement = row.getElement('.infoBarOption' + optionIndex);

        optionElement.setProperty('text', text);

        var onclick = function ()
        {
            method();
            InfoBar.destroyRowByRowElement(row);
        };

        optionElement.addEvent('click', onclick);

        optionElementWrapper.style.visibility = 'visible';
    }

    if (sticky)
    {
        var bulletElement = row.getElement('.infoBarBullet');

        bulletElement.removeClass('standard');
        bulletElement.addClass('sticky');

        bulletElement.title = "Sticky note - click dismiss button (x) to dismiss";
    }

    if (closable === false)
    {
        row.getElement('.infoBarClose').destroy();
    }

    row.inject(this.infoBarContainer);

    this.infoBarElement.style.display = 'block';

    this.currentMessages.push({ element: row, uniqueID: uniqueID, sticky: sticky });

    var index = this.currentMessages.length - 1;

    //var timerID = setTimeout(function() { InfoBar.destroyRow(index); }, this.defaultTime);
    //this.timerID = setTimeout(this.timer_ontick, this.defaultTime);
    this.resetTimer(true)

    this.infoBarElement.style.display = 'block';

    //this.zebra();
}

// Destroys the visible infobar elements
InfoBar.destroy = function()
{
    if (this != InfoBar)
        return InfoBar.destroy.call(InfoBar); // use context

    if (!this.initialized)
        return;

    if (this.infoBarElement.isDisplayed() == false)
        return;

    // destroy all rows
    for (var i = this.currentMessages.length - 1; i >= 0; i--)
    {
        var obj = this.currentMessages[i];

        // check for a sticky message. - do not remove them on tick
        if (!obj.sticky)
        {
            obj.element.destroy();
            this.currentMessages.erase(obj);
        }
    }

    // only if a sticky message is not present.
    if (this.currentMessages.length == 0)
        this.infoBarElement.style.display = 'none';
}

// Destroys a row in the info bar based on element
InfoBar.destroyRowByRowElement = function(row)
{
    // last row? hide box
    if (this.currentMessages.length === 1)
        this.infoBarElement.style.display = 'none';

    var objectArray = this.currentMessages;

    for (var i = 0; i < objectArray.length; i++)
    {
        var obj = objectArray[i];

        if (obj.element === row)
        {
            obj.element.destroy();
            this.currentMessages.erase(obj);
        }
    }
}

// Destroys a row in the info bar based a unique ID
InfoBar.destroyRowByUniqueID = function (uniqueID)
{
    var objectArray = this.currentMessages;

    if (!objectArray)
        return false;

    for (var i = 0; i < objectArray.length; i++)
    {
        var obj = objectArray[i];

        if (obj.uniqueID == uniqueID)
        {
            this.destroyRowByRowElement(obj.element);

            //this.zebra();

            return true;
        }
    }

    return false;
}

// Initializes the info bar. Sets up the row html, necessary cached pointers and events
InfoBar.initialize = function()
{
    if (this.initialized) return;

    this.defaultTime = 6500;
    this.maxMessages = 3;

    this.rowTemplate = new Element('div');
    this.rowTemplate.className = 'infoBarRow';
    this.rowTemplate.innerHTML = '<div class="infoBarBullet standard spriteCalendar">&nbsp;</div><div class="infoBarMessage">message</div><div class="infoBarOption2Wrapper"><span class="infoBarOption2 customHyperLink"></span>&nbsp;</div><div class="infoBarOption1Wrapper"><span class="infoBarOption1 customHyperLink"></span>&nbsp;</div><div class="infoBarClose spriteCalendar" onclick="InfoBar.destroyRowByRowElement(this.parentNode);" title="dismiss"></div><br clear="all" />';
    this.infoBarContainer = $('infoBarContainer');

    this.infoBarElement = $('infoBar');

    this.currentMessages = [];

    this.timer = null;

    this.initialized = true;

    this.infoBarElement.addEvents({
        'mouseenter': function()
        {
            InfoBar.resetTimer(false);
        },
        'mouseleave': function()
        {
            InfoBar.resetTimer(true);
        }
    });
}

// Starts, re-starts or pauses the timer
InfoBar.resetTimer = function(restart)
{
    if (this.timerID)
        clearTimeout(this.timerID);

    if (restart)
        this.timerID = setTimeout(this.timer_ontick, this.defaultTime);
}

// The method called when a timer times out (reaches its destination in ms)
InfoBar.timer_ontick = function()
{
    if (this != InfoBar)
        return InfoBar.timer_ontick.call(InfoBar); // use context

    // destroy all rows
    for (var i = this.currentMessages.length - 1; i >= 0; i--)
    {
        var obj = this.currentMessages[i];

        // check for a sticky message. - do not remove them on tick
        if (!obj.sticky)
        {
            obj.element.destroy();
            this.currentMessages.erase(obj);
        }
    }

    // only if a sticky message is not present.
    if (this.currentMessages.length == 0)
        this.infoBarElement.style.display = 'none';
}

// Zebra-separates alternating rows.
// Not currently used.
//InfoBar.zebra = function()
//{
//    return;
//    //        alert(this.currentMessages.length);
//    // apply zebra of underlinse
//    if (this.currentMessages.length > 1)
//    {
//        for (var i = 0; i < this.currentMessages.length - 1; i++)
//        {
//            this.currentMessages[i].element.className = "infoBarRow bordered";
//        }
//    }
//    else
//    {
//        this.currentMessages[0].element.className = "infoBarRow";
//    }
//}

Array.prototype.joinWithFinalWord = function (finalWord)
{
    if(this.length == 0)
        return '';
    
    if(this.length == 1)
        return this[0].toString().trim();

    var text = '';

    for(var i = 0, len = this.length; i < len; i++)
    {
        if(i < len - 1)
            text += this[i] + ', ';
        else
            text = text.substr(0,text.length - 2) + ' ' + finalWord + ' ' + this[i];
    }

    return text.trim();
}

String.prototype.removeLastCommaWithAnd = function ()
{
    var str = this.removeLastComma();

    if (str.contains(','))
    {
        var lastCommaIndex = str.lastIndexOf(',');
        str = str.substr(0, lastCommaIndex).trim() + ' and ' + str.substr(lastCommaIndex + 1).trim();

        return str;
    }

    return str;
}

String.prototype.removeLastComma = function()
{
    var str = this.trim();

    if(str.endsWith(','))
    {
        return str.substr(0,str.length-1);
    }

    return this;
}

/// Breaks up strings beyond maxCharacters with soft hyphens for ideal formatting.
/// NOTE: Use only where the modern css property 'word-wrap: break-word' does not work properly.
String.prototype.softHyphenate = function (maxCharacters)
{
    if (isNaN(maxCharacters))
    {
        alert('Call to String.softHyphenate(maxCharacters) has invalid arguments.');
        return;
    }

    var split = maxCharacters / 2;
    var partSizes = [split, split];

    if (maxCharacters % 2 > 0)
        partSizes[1] += 1;

    var pattern = new RegExp('([^\\s-]{' + partSizes[0] + '})([^\\s-]{' + partSizes[1] + '})', 'gm');
    var replacement = '$1&shy;$2';

    return this.replace(pattern, replacement);
}

///  returns the first occurence of a string between two other strings
String.prototype.between = function (pre, post)
{
    var preIndexWithLength = this.indexOf(pre) + pre.length;
    
    return this.substr(preIndexWithLength, this.indexOf(post) - preIndexWithLength);
}

String.prototype.removeTokenizedString = function (pre, post)
{
    return this.substr(0, this.indexOf(pre)) + this.substr(this.indexOf(post) + post.length);
}

String.prototype.caseInsensitiveEquals = function (compareString)
{
    if (compareString === null)
        return false;

    return this.removeWhiteSpace().toLowerCase() == compareString.removeWhiteSpace().toLowerCase();
}

String.prototype.removeWhiteSpace = function ()
{
    return this.replace(/\s/g, '');
}

String.prototype.formatAsInt = function (failAsZero)
{
    var value = this.removeWhiteSpace();

    if (!isNaN(parseInt(value, 10)))
        return parseInt(value, 10).toString();

    if (failAsZero)
        return 0;

    return null;
}

String.prototype.formatAsDecimal = function (fractionDigits)
{
    if (!fractionDigits)
        fractionDigits = 2;

    var value = this.removeWhiteSpace().replace(/[\$,]/g, '');

    if (!isNaN(parseFloat(value)))
        return parseFloat(value).toFixed(fractionDigits).toString();

    if (value == '')
        return new Number(0).toFixed(fractionDigits);

    return null;
}

String.prototype.removeNonDecimalPriceDetails = function ()
{
    return this.replace(/[,\$\s]/g, '');
}

String.prototype.formatAsPrice = function (includeDollar)
{
    var prefix = includeDollar ? '$' : '';

    if (this == '')
        return '';

    var numberString = this;

    if (Math.ceil(this) - this > 0) // Price has a decimal part
        numberString = parseFloat(this).toFixed(2);
    else
        numberString = parseFloat(this).toFixed(0);

    return prefix + Common.addCommasToNumber(numberString);
}

Number.prototype.formatAsPrice = function (includeDollar)
{
    return this.toString().formatAsPrice(includeDollar);
}

Common.addCommasToNumber = function (nStr)
{
    nStr += '';

    if (nStr.indexOf(',') > -1)
        return nStr;

    x = nStr.split('.');
    x1 = x[0];
    x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1))
    {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
}


var Browser = {};

Browser.InternetExplorer = {};
Browser.Firefox = {};
Browser.Safari = {};
Browser.Opera = {};
Browser.Chrome = {};

Browser.agent = null;
Browser.hasDebuggerStatement = false;
Browser.name = navigator.appName;
Browser.version = parseFloat(navigator.appVersion);

if (navigator.userAgent.indexOf(' MSIE ') > -1)
{
    Browser.agent = Browser.InternetExplorer;
    Browser.version = parseFloat(navigator.userAgent.match(/MSIE (\d+\.\d+)/)[1]);
    Browser.hasDebuggerStatement = true;
}
else if (navigator.userAgent.indexOf(' Firefox/') > -1)
{
    Browser.agent = Browser.Firefox;
    Browser.version = parseFloat(navigator.userAgent.match(/ Firefox\/(\d+\.\d+)/)[1]);
    Browser.name = 'Firefox';
    Browser.hasDebuggerStatement = true;
}
else if (/chrome/.test(navigator.userAgent.toLowerCase()))
{
    Browser.agent = Browser.Chrome;
    Browser.name = 'Chrome';
}
else if (navigator.userAgent.indexOf(' Safari/') > -1)
{
    Browser.agent = Browser.Safari;
    Browser.version = parseFloat(navigator.userAgent.match(/ Safari\/(\d+\.\d+)/)[1]);
    Browser.name = 'Safari';
}
else if (navigator.userAgent.indexOf('Opera/') > -1)
{
    Browser.agent = Browser.Opera;
}

Common.getLocalFormattedDate = function(date, format) 
{
    return date.localeFormat(format);
}

var Validate = {};
Validate.expressions = {
    regex12Hour: /^([1-9]|1[0-2]){1}(:[0-5][0-9]){1}\s{0,1}([ap]m){1}$/i,
    regex24Hour: /^([0-9]|1[0-9]|2[0-3]){1}(:[0-5][0-9]){1}$/i,
    price: /^\$?\d[\d,]*\.?\d{0,2}$/ 
};

Validate.isValidPrice = function (price, minPrice, maxPrice)
{
    if (typeof price === 'number')
        price = price.toString();

    var isValid = Validate.expressions.price.test(price);

    if (isValid)
    {
        var decimalPrice = parseFloat(price.replace(/[\$,\s]/g, ''));

        if ((minPrice || minPrice === 0) && decimalPrice < minPrice)
            isValid = false;

        if ((maxPrice || maxPrice === 0) && decimalPrice > maxPrice)
            isValid = false;
    }

    return isValid
}

Validate.isValidDate = function (date, minDate, maxDate)
{
    // short circuit with insufficient data
    if (typeof date === 'undefined' || !date)
        return false;

    if (typeof date === 'string')
        date = Validate.convertStringToDate(date);

    if (isNaN(date))
        return false;

    if (minDate)
    {
        if (typeof minDate === 'string')
            minDate = Validate.convertStringToDate(minDate);

        if (isNaN(minDate))
        {
            alert('Minimum date is invalid.');
            return false;
        }

        if (minDate.getTime() > date.getTime())
            return false;
    }

    if (maxDate)
    {
        if (typeof maxDate === 'string')
            maxDate = Validate.convertStringToDate(maxDate);

        if (isNaN(maxDate))
        {
            alert('Maximum date is invalid.');
            return false;
        }

        if (maxDate.getTime() < date.getTime())
            return false;
    }

    return true;
}

Validate.convertStringToDate = function (dateString)
{
    return Date.parse(dateString);
}

Validate.isValidTimeString = function (timeString)
{
    if (!timeString || timeString.length === 0 || !(Validate.expressions.regex12Hour.test(timeString) ^ Validate.expressions.regex24Hour.test(timeString))) // if it's not one or the other (xor)
        return false;

    return true;
}

Validate.isValidTimeRange = function (minTimeString, maxTimeString)
{
    if (!Validate.isValidTimeString(minTimeString))
        return false;

    if (!Validate.isValidTimeString(maxTimeString))
        return false;

    // 12:00am max will save as 11:59pm, thus 11:59pm is not a valid start time
    if (minTimeString.replace(/\s/g, '').toLowerCase() == '11:59pm')
        return false;

    // Midnight will save as 11:59pm, but we allow it to be valid since it comes after all other times in the day span
    if (maxTimeString.replace(/\s/g, '').toLowerCase() == '12:00am')
        return true;

    // Assert there is a time span greater than 0
    if (Date.parse('1/1/1994 ' + minTimeString).getTime() >= Date.parse('1/1/1994 ' + maxTimeString).getTime())
        return false;

    return true;
}

Validate.isValidNumber = function (number, min, max)
{
    number = parseInt(number, 10);
    if( min !== 0 )
        min = min ? parseInt(min, 10) : null;
    if( max !== 0 )
        max = max ? parseInt(max, 10) : null;

    if(isNaN(number))
        return false;

    if( min !== null && !isNaN(min))
        if(min > number)
            return false;

    if (max !== null && !isNaN(max))
        if(max < number)
            return false;

    return true;
}

Validate.isValidPercentage = function (number)
{
    number = number.toString().trim();

    // Remove percent character
    if (number.endsWith('%'))
        number = number.substr(0, number.length - 1);

    return Validate.isValidNumber(number, 0, 100);
}

Common.scrollDocumentToElement = function (element, relativeElement)
{
    if (!relativeElement)
        relativeElement = $(document.documentElement);

    var elementY = $(element).getPosition(relativeElement).y;

    relativeElement.scrollTo(relativeElement.getScroll().x, elementY);
    //    var hourElement = $(document.body).getElement('div[hour=' + hour + ']');
    //    var masterTimeCellWrapper = $('masterTimeCellWrapper');
    //    var scrollDistanceY = hourElement.getPosition(masterTimeCellWrapper).y;

    //    masterTimeCellWrapper.scrollTo(0, scrollDistanceY);
};
