
// Get the namespace.
window.EXANIMO = window.EXANIMO || {};




/**
 *
 * Adapted from the YAHOO Global Object.
 * <http://developer.yahoo.com/yui/docs/yahoo/YAHOO.html>
 *
 *  Returns the namespace specified and creates it if it doesn't exist
 *
 *     @param ns    the name of the namespace
 *     @return      a reference to the namespace object
 *
 */
EXANIMO.namespace = function(ns)
{
    if (!ns || !ns.length)
        return;

    var levels = ns.split('.');
    var nsobj = EXANIMO;

    // EXANIMO is implied, so it is ignored if it is included
    for (var i = (levels[0] == 'EXANIMO') ? 1 : 0; i < levels.length; i++)
    {
        nsobj[levels[i]] = nsobj[levels[i]] || {};
        nsobj = nsobj[levels[i]];
    }

    return nsobj;
}




/**
 *
 * EXANIMO.utils.getBrowserInfo
 *
 *     Stupid crappy browser sniffing crap.
 *
 *     @author     matthew at exanimo dot com
 *     @version    2006.07.25
 *
 */

    EXANIMO.namespace('EXANIMO.utils');

    /**
     *
     * Gets the browser information.
     *
     *     @return    an object with a browser and a version property
     *
     */
    EXANIMO.utils.getBrowserInfo = function()
    {
        if (navigator.appName.indexOf('Microsoft Internet') != -1)
            return {browser: 'MSIE'};
        if (navigator.userAgent.indexOf('Safari') != -1)
            return {browser: 'SAFARI'};
        else
            return {browser: 'OTHER'};
    }




/**
 *
 * EXANIMO.utils.getSWFObject
 *
 *     From <http://www.permadi.com/tutorial/flashjscommand/>
 *
 */

    EXANIMO.namespace('EXANIMO.utils');

    /**
     *
     * Gets a SWF from the DOM.
     *
     *     @param id    the name/id of the movie
     *     @return      the swf object
     *
     */
    EXANIMO.utils.getSWFObject = function(id)
    {
        if (window.document[id])
            return window.document[id];
        if (navigator.appName.indexOf('Microsoft Internet') == -1)
            if (document.embeds && document.embeds[id])
                return document.embeds[id];
        else
            return document.getElementById(id);
    }




/**
 *
 * StateManager
 *
 *     Adds state management (back button and deep-linking) to your single-page
 *     web application.
 *
 *     For usage instructions, see
 *     <http://exanimo.com/javascript/using-the-statemanager-in-ajax-apps>.
 *
 *     copyright 2006 matthew john tretter.  available under the MIT License.
 *     (<http://www.opensource.org/licenses/mit-license.php>)
 *
 *     todo:
 *         - pick one of _cancelLoad or _refreshPage
 *         - clean up code
 *             - look at all the stuff that hasn't been looked at since July
 *               (_updateIFrame, etc.) and make sure it's the best way.
 *         - OO changes
 *             - reduce redundancy (less "EXANIMO.managers.StateManager")
 *             - use Function instead of Object.
 *
 *     version history:
 *         2006.11.16
 *             - fixed bug introduced in last version that broke deeplinks for
 *               Safari.
 *         2006.11.12
 *             - added AJAX autoInit
 *             - logState is now setState
 *             - added new events 'stateRegistered', 'stateSet'
 *         2006.11.10
 *             - major updates. loadState is now logState. there was no reason
 *               to load a state without logging it -- that's the same as
 *               calling the stateChange handler.
 *         2006.11.09
 *             - stateChange event now tells you if the state was changed
 *               directly or indirectly (by back, forward, or a deeplink)
 *         2006.11.08
 *             - fixed a bug that caused safari to load states twice
 *         2006.11.07
 *             - some misc. bugs fixes / tons of cleanup
 *             - registerState is gone in favor of setTitle
 *         2006.11.06
 *             - renamed to StateManager
 *             - now supports AJAX
 *         2006.11.05
 *             - FULL Safari support!!!
 *         2006.10.31
 *             - reworked hash method to eliminate FP9 bugs.
 *             - defineState is now registerState
 *         2006.10.30
 *             - added defineState to fix a problem with deep linking
 *         2006.09.25
 *             - added initialize function
 *             - now uses object literal
 *         2006.09.14
 *             - BACK BUTTON FIXED FOR SAFARI!! unfortunately, hash no longer
 *               updates, though you can still deep-link if you know the hash.
 *         2006.09.13
 *             - if movie is not provided, checks for variable filled by
 *               JSInterface's locateSelf.
 *         2006.07.27
 *             - renamed from BB5K
 *             - got rid of #lookup= in hash.
 *         2006.07.20
 *
 *     @author     matthew at exanimo dot com
 *     @version    2006.11.12
 *
 */

EXANIMO.namespace('EXANIMO.managers');
if (!EXANIMO.managers.StateManager)
{
    EXANIMO.managers.StateManager = {


        CHECK_RATE: 100,
        stateChangeHandler: null,
        event: null,

        _initialized: false,
        _autoInitInterval: setInterval(
            function()
            {
                if (document.body && !EXANIMO.managers.StateManager._initialized)
                {
                    EXANIMO.managers.StateManager.initialize();
                }
            },
            10
        ),
        _checkInterval: null,
        _method: null,
        _swf: null,

        // Browser specific variables.
        _oldStateID: null,
        _refreshPage: null,
        _cancelLoad: false,


        /**
         *
         * Does all the behind-the-scenes startup work for the StateManager.
         *
         *     @param swf    (optional) the SWF to add state management to. if
         *                   not present, the function will use the swf in
         *                   EXANIMO.utils.JSInterface.swfObject. if that is
         *                   also empty, it goes into AJAX mode.
         *
         */
        initialize: function(swf)
        {
            // Stop trying to auto-initialize and reset StateManager.
            clearInterval(EXANIMO.managers.StateManager._autoInitInterval);
            clearInterval(EXANIMO.managers.StateManager._checkInterval);
            EXANIMO.managers.StateManager._initialized = true;

            // If a SWF was provided, remember it.
            if (typeof swf != 'undefined')
            {
                EXANIMO.managers.StateManager._swf = typeof(swf) == 'string' ? EXANIMO.utils.getSWFObject(swf) : swf || null;
            }

            // Decide which method to use by (blech!) browser sniffing.
            var browserInfo = EXANIMO.utils.getBrowserInfo();
            switch(browserInfo.browser)
            {
                case 'MSIE':
                    EXANIMO.managers.StateManager._method = 'IFRAME';
                    break;
                case 'SAFARI':
                    EXANIMO.managers.StateManager._method = 'LINK';
                    break;
                default:
                    EXANIMO.managers.StateManager._method = 'HASH';
                    break;
            }

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'HASH':

                    var initialState = EXANIMO.managers.StateManager._getStateID();
                    EXANIMO.managers.StateManager._oldStateID = initialState == 'home' ? 'home' : null;

                    // Watch to see if the hash changes.
                    var checkForHashChange = function()
                    {
                        var stateID = EXANIMO.managers.StateManager._getStateID();

                        if (stateID != EXANIMO.managers.StateManager._oldStateID)
                        {
                            EXANIMO.managers.StateManager._oldStateID = stateID;
                            EXANIMO.managers.StateManager._dispatchEvent(stateID)
                        }
                    }

                    EXANIMO.managers.StateManager._checkInterval = setInterval(checkForHashChange, EXANIMO.managers.StateManager.CHECK_RATE);

                    break;

                //
                // Do the actual initialization, based on the method determined
                // above.
                //
                case 'IFRAME':

                    //
                    // Make sure the iframe knows that, when it loads, it should
                    // not refresh this page.
                    //
                    EXANIMO.managers.StateManager._refreshPage = false;

                    // Create and attach the iframe.
                    var iframe = document.createElement('iframe');
                    iframe.setAttribute('src', 'about:blank');
                    iframe.setAttribute('name', 'EXANIMO-managers-StateManager-iFrame');
                    iframe.setAttribute('id', 'EXANIMO-managers-StateManager-iFrame');
                    iframe.style.visibility = 'hidden';
                    iframe.style.width = '0';
                    iframe.style.height = '0';
                    iframe.style.position = 'absolute';
                    iframe.style.overflow = 'hidden';
                    document.body.appendChild(iframe);

                    // If a state id is already present in the hash, go to it.
                    var stateID = EXANIMO.managers.StateManager._getStateID();
                    if (stateID != 'home')
                    {
                        setTimeout(
                            function()
                            {
                                EXANIMO.managers.StateManager._dispatchEvent(stateID)
                            },
                            0
                        );
                    }

                    // Update the page and hash from the iframe.
                    frames['EXANIMO-managers-StateManager-iFrame'].document.open();
                    if (stateID)
                        frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = "' + (stateID == 'home' ? '' : stateID) + '"; parent.EXANIMO.managers.StateManager._updateIFrame("' + stateID + '");</script>');
                    else
                        frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = ""; parent.EXANIMO.managers.StateManager._updateIFrame();</script>');
                    frames['EXANIMO-managers-StateManager-iFrame'].document.close();

                    break;

                case 'LINK':

                    document.location.EXANIMO = document.location.EXANIMO || {};
                    document.location.EXANIMO.managers = document.location.EXANIMO.managers || {};
                    document.location.EXANIMO.managers.StateManager = document.location.EXANIMO.managers.StateManager || {};

                    var loc = document.location.EXANIMO.managers.StateManager;

                    // Make sure the last state is loaded when you come back to
                    // this page after navigating away.
                    window.onunload = function()
                    {
                        loc.oldHistoryLength = -1;
                    }

                    if (loc.deepLink && loc.deepLink != 'home')
                    {
                    	loc.oldHistoryLength = -1;
						loc.deepLink = null;
                    }

                    // Create a list of the states we click through.
                    if (typeof loc.stateList == 'undefined')
                    {
                        loc.stateList = [EXANIMO.managers.StateManager._getStateID() || 'home'];
                        loc.deepLink = loc.stateList[0];

                        loc.offset = history.length - 1;
                        while (loc.offset)
                        {
                            loc.stateList.unshift(null);
                            loc.offset--;
                        }
                        delete loc.offset;

                        loc.oldHistoryLength = document.location.hash ? -1 : history.length;

                    }

                    // Watch to see if the length of the history object changes.
                    var checkForHistoryLengthChange = function()
                    {
                        var loc = document.location.EXANIMO.managers.StateManager;

                        if (EXANIMO.managers.StateManager._cancelLoad)
                        {
                            EXANIMO.managers.StateManager._cancelLoad = false;
                            loc.oldHistoryLength = history.length;
                            return;
                        }

                        if (history.length != loc.oldHistoryLength)
                        {
                            var stateID = loc.stateList[history.length - 1];

                            EXANIMO.managers.StateManager._dispatchEvent(stateID)
                            loc.oldHistoryLength = history.length;
                        }
                    }

                    EXANIMO.managers.StateManager._checkInterval = setInterval(checkForHistoryLengthChange, EXANIMO.managers.StateManager.CHECK_RATE);

                    break;

            }
        },


        /**
         *
         * Adds an entry to the history.
         *
         *     @param stateID       a unique identifier corresponding to a
         *                          specific state
         *
         */
        setState: function(stateID, title)
        {
            // Set the title.
            if (title)
            {
                EXANIMO.managers.StateManager.setTitle(title);
            }

            // Block infinite loops.
            if (EXANIMO.managers.StateManager.event) return;

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'HASH':

                    document.location.hash = stateID == 'home' ? '#' : stateID;
                    EXANIMO.managers.StateManager._oldStateID = stateID;

                    break;

                case 'IFRAME':

                    EXANIMO.managers.StateManager._setIFrame(stateID);

                    break;

                case 'LINK':

                    EXANIMO.managers.StateManager._cancelLoad = true;

                    var a = document.createElement('a');
                    a.setAttribute('href', stateID == 'home' ? '#' : '#' + stateID);

                    var evt = document.createEvent('MouseEvents');
                    evt.initEvent('click', true, true);
                    a.dispatchEvent(evt);

                    document.location.EXANIMO.managers.StateManager.stateList.push(stateID);

                    break;

            }

            // Dispatch the stateChange events
            EXANIMO.managers.StateManager._dispatchEvent(stateID, true);

        },


        /**
         *
         * Sets the window's title.
         *
         *     @param title    the string to put in the browser's title bar
         *
         */
        setTitle: function(title)
        {
            window.document.title = title || ' ';
        },


        /**
         *
         * Dispatches a stateChange event.
         *
         *     @param stateID    the id of the state to load
         *     @param manual     was the stateChange manual? manual changes
         *                       are the result of calls to setState. (automatic
         *                       changes are the result of the back / forward
         *                       buttons or deep-linking.)
         *
         */
        _dispatchEvent: function(stateID, manual)
        {
            stateID = stateID || 'home';

            // If a SWF reference has been stored using the JSInterface's
            // locateSelf (and the StateManager wasn't initialized with a swf
            // argument), use that.
            if ((typeof EXANIMO.utils.JSInterface != 'undefined') && (EXANIMO.utils.JSInterface.swfObject))
            {
                EXANIMO.managers.StateManager._swf = EXANIMO.managers.StateManager._swf || EXANIMO.utils.JSInterface.swfObject;
            }

            // Flash: call the SWF's loadState function..
            if (EXANIMO.managers.StateManager._swf)
            {
                // ..but only if
                //     1) it hasn't been caused by navigating within flash. (if
                //        it has, we don't want to navigate again.)
                //     2) a handler has been set
                if (!manual && EXANIMO.managers.StateManager._swf.dispatchStateChangeEvents)
                {
                    EXANIMO.managers.StateManager._swf.dispatchStateChangeEvents(stateID);
                }
            }

            // AJAX: dispatch a stateChange event.
            else
            {
                EXANIMO.managers.StateManager.event = {id: stateID};

                // If a handler is set, call it.
                if (EXANIMO.managers.StateManager.stateChangeHandler)
                {
                    EXANIMO.managers.StateManager.stateChangeHandler(EXANIMO.managers.StateManager.event);
                }

                if (manual)
                {
                    if (EXANIMO.managers.StateManager.stateSetHandler)
                    {
                        EXANIMO.managers.StateManager.stateSetHandler(EXANIMO.managers.StateManager.event);
                    }
                }
                else
                {
                    if (EXANIMO.managers.StateManager.stateRevisitedHandler)
                    {
                        EXANIMO.managers.StateManager.stateRevisitedHandler(EXANIMO.managers.StateManager.event);
                    }
                }

                EXANIMO.managers.StateManager.event = null;
            }
         },


        /**
         *
         * Retrieves state ID.
         *
         *     @return    the id of the current state
         *
         */
        _getStateID: function()
        {
            return document.location.href.split('#')[1] || 'home';
        },


        /**
         *
         * Sets the iframe to a specific lookup.
         *
         *     @param lookup    the lookup to use
         *
         */
        _setIFrame: function(stateID)
        {
            EXANIMO.managers.StateManager._refreshPage = false;
            var iframe = document.getElementById('EXANIMO-managers-StateManager-iFrame');

            switch(EXANIMO.managers.StateManager._method)
            {
                case 'IFRAME':

                    frames['EXANIMO-managers-StateManager-iFrame'].document.open();
                    frames['EXANIMO-managers-StateManager-iFrame'].document.write('<script>parent.document.location.hash = "' + (stateID == 'home' ? '#' : stateID) + '"; /* Wait for IE to impose its title before setting ours. */ setTimeout( function(){ parent.EXANIMO.managers.StateManager._updateIFrame("' + stateID + '"); }, 0);</script>');
                    frames['EXANIMO-managers-StateManager-iFrame'].document.close();

                    break;
            }
        },


        /**
         *
         * Updates the iframe
         *
         *     @param stateID    the id of the state to record
         *
         */
        _updateIFrame: function(stateID)
        {
            if (EXANIMO.managers.StateManager._refreshPage)
            {
                EXANIMO.managers.StateManager._dispatchEvent(stateID);
            }
            else
            {
                EXANIMO.managers.StateManager._refreshPage = true;
            }
        }




    }




}




// Copyright (c) 2006 matthew john tretter.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/**
 *  The CustomEvent class provides a simple event framework.  Code can subscribe to
 *  an event instance, and when the event is fired, all subscriber functions are called
 */
var CustomEvent = function() {
	this._arrSubscribers = [];
}
CustomEvent.prototype = {

/**
 * Call all subscribers functions.
 * param obj:  an option argument to be passed to the subscriber functions
 */
	fire: function(obj) {
		for (var i = 0; i < this._arrSubscribers.length; i++) {
			var function_subscriber = this._arrSubscribers[i];

			function_subscriber(obj);
		}
	},

/**
 * Subscribe to this event.  eventHandler function will be called when the event is fired
 */
	subscribe: function(function_eventHandler) {
		this._arrSubscribers.push(function_eventHandler);
	}
}


/**
 * State is change when the user clicks the BACK or FORWARD browser button
 * subscribers to STATE_CHANGED_EVENT are responsible to inspecting the new state
 * and updating the UI, if necessary
 */
var STATE_CHANGED_EVENT = new CustomEvent("state changed event");
EXANIMO.managers.StateManager.stateChangeHandler = function(e)
{
	STATE_CHANGED_EVENT.fire(e);
}

/**
 * Disable the auto initialization of the state manager
 * we want to initialize ourselves after everything else has been initialized
 */
clearInterval(EXANIMO.managers.StateManager._autoInitInterval);

/**
 * The PAGE_INITIALIZED_EVENT is fired after everything on the page has been initialized.
 * subscribe to this event if you to initialize a service that requires other services to be initialized
 */
var PAGE_INITIALIZED_EVENT = new CustomEvent("page initialized event");

/**
 * initialize the state manager after everything else has been initialized.
 * we need to allow the pandora pagers to subscribe to the STATE_CHANGED_EVENT
 * prior to initializing the state manager
 */
PAGE_INITIALIZED_EVENT.subscribe(function(e)
{
	EXANIMO.managers.StateManager.initialize();
});


/**
 * The USER_INTERACTION_EVENT is fired whenever the user interacts with a backstage page:
 *	- when the page through a table
 *  - when they add a bookmark
 */
var USER_INTERACTION_EVENT = new CustomEvent("USER_INTERACTION_EVENT");

USER_INTERACTION_EVENT.subscribe(function(e)
{
	Patron.refreshAds();
});


function expandBio(expand) {
	if (expand) {
		$("#bio_end").show();
		$("#see_all_link").hide();
		$("#shorten_link").show();
	} else {
		$("#bio_end").hide();
		$("#see_all_link").show();
		$("#shorten_link").hide();
	}
}

function expandAlbumGrid(expand) {
	if (expand) {
		$("#album_grid_end").show();
		$("#album_grid_see_all_link").hide();
		$("#album_grid_shorten_link").show();
	} else {
		$("#album_grid_end").hide();
		$("#album_grid_see_all_link").show();
		$("#album_grid_shorten_link").hide();
	}
}

function showStationDescriptionForm(expand) {
	if (expand) {
		$("#station_description_form").show();
		$("#show_description_form").hide();
		$("#community_section_title_description").hide();
		$("#station_details").hide();
		$("#hide_description_form").show();
		$("#hide_station_name_form").show();
		$("#station_description_text").hide();
	} else {
		$("#station_description_form").hide();
		$("#show_description_form").show();
		$("#community_section_title_description").show();
		$("#station_details").show();
		$("#hide_description_form").hide();
		$("#hide_station_name_form").hide();
		$("#station_description_text").show();
	}
}

function showCheckboxesFriend() {
	$('.profile_friend_checkbox').show();
	$('#remove_checked_friends').show();
	return false;
}

function hideCheckboxesFriend() {
	$('.profile_friend_checkbox').hide();
	$('#remove_checked_friends').hide();
	return false;
}

function showCheckboxesTrack() {
	$('.profile_track_buy').hide();
	$('.profile_track_checkbox').show();
	$('#remove_checked_tracks').show();
	return false;
}

function hideCheckboxesTrack() {
	$('.profile_track_buy').show();
	$('.profile_track_checkbox').hide();
	$('#remove_checked_tracks').hide();
	return false;
}

function showCheckboxesArtist() {
	$('.profile_artist_checkbox').show();
	$('#remove_checked_artists').show();
	return false;
}

function hideCheckboxesArtist() {
	$('.profile_artist_checkbox').hide();
	$('#remove_checked_artists').hide();
	return false;
}

function showQuickAddForm(showing, hiding) {
	$(showing).show();
	$(hiding).hide();
}

function quickAdd_onclick(htmlAnchor, function_callback)
{
	var imgQuickAdd_array = $("img", htmlAnchor);
	if (imgQuickAdd_array && imgQuickAdd_array.length == 1)
	{
		imgQuickAdd_array[0].src = "/images/searching_anim_70.gif";
		imgQuickAdd_array[0].onmouseout = null;
		imgQuickAdd_array[0].onmouseover = null;


		if (window.setTimeout)
		{
			window.setTimeout(function() {
				function_callback();
			}, 100);
		}
		else
		{
			function_callback();
		}
	}
	else
	{
		function_callback();
	}

}

function expandHiddenRows(expand, visibleRows, tableSelector, seeAllSelector, shortenSelector) {
	var rows = $(tableSelector + " tr");
	for (var i = visibleRows + 1; i < rows.size(); i++) {
		if (expand) {
			$(rows.get(i)).show();
		} else {
			$(rows.get(i)).hide();
		}
	}
	if (expand) {
		$(seeAllSelector).hide();
		$(shortenSelector).show();
	} else {
		$(seeAllSelector).show();
		$(shortenSelector).hide();
	}
	return false;
}

function prefGlobalEmailChange(checkbox) {
	if (checkbox.checked) {
		getDiv("emailCheckBoxes").className = "";
		getDiv("emailOptIn").checked = true;
		getDiv("notifyOnNote").checked = true;
		getDiv("emailOptIn").disabled = false;
		getDiv("notifyOnNote").disabled = false;
	} else {
		getDiv("emailCheckBoxes").className = "emailDisabled";
		getDiv("emailOptIn").checked = false;
		getDiv("notifyOnNote").checked = false;
		getDiv("emailOptIn").disabled = true;
		getDiv("notifyOnNote").disabled = true;
	}
}

function noteTextChanged(textarea, max, noteCharCount, noteCharCountDiv, classTooLong) {
	var count = textarea.value.length;
	getDiv(noteCharCount).value = count;
	if (count > max) {
		getDiv(noteCharCountDiv).className = classTooLong;
	} else {
		getDiv(noteCharCountDiv).className = "";
	}
}


// code to watch notes for character count changes
var listOfNoteChangeCheckers = {}

var NoteTextChangeChecker = function(objToWatch, maxNoteSize) {
    this.objToWatch = objToWatch;
	this.maxNoteSize = maxNoteSize;
	listOfNoteChangeCheckers[objToWatch.id] = this;
}

NoteTextChangeChecker.prototype = {
	isReady : false,
	check : function() {
		if (this.isReady) {
		   noteTextChanged(this.objToWatch, this.maxNoteSize, 'noteCharCount', 'noteCharCountDiv', 'noteTooLong');
		   var thisObj = this;
		   setTimeout(function() {thisObj.check()},300);
		}
	}
}

function startCheckNoteTextChanged(obj){
	var thisChecker = listOfNoteChangeCheckers[obj.id];
    if (typeof (thisChecker) == "undefined") {
		thisChecker = new NoteTextChangeChecker(obj);
    }
	thisChecker.isReady = true;
	thisChecker.check();
}

function stopCheckNoteTextChanged(obj){
	var thisChecker = listOfNoteChangeCheckers[obj.id];
	thisChecker.isReady = false;
}
// -- END / code to watch notes for character count changes


// Render some HTML rows in a "fans of" table, taking data from a JS object.
// The object is created by the #fansOf macro and has the format:
// ( (isOnline, "webname", "fullname", ("artist1", "artist2", ...)), ... )
function renderFansOf(tableSelector, fans, numRows) {
	var count = 0;
	var picked = [];
	while (count < Math.min(fans.length, numRows)) {
		var r = Math.floor(Math.random() * fans.length);
		if (picked[r]) continue;
		count++;
		picked[r] = true;
		var fan = fans[r];
		var row = '<tr>';
		row += '<td align="left" valign="top">';
		if (fan[0]) {
			row += '<img src="/images/online_status_on.gif" width="24" height="18" alt="currently online" title="currently online">';
		} else {
			row += '<img src="/images/online_status_off.gif" width="24" height="18" alt="currently offline" title="currently offline">';
		}
		row += '</td>'
		row += '<td align="left" valign="top">';
		row += '<div style="width:144px; word-wrap:break-word">';
		// Without this DIV, word-wrapping does not work in Safari
		row += '<b><a href="/people/' + fan[1] + '">' + fan[2] + '</a></b><br />';
		if (fan[3].length > 0) {
			row += '(also listening to:	';
			for (var i = 0; i < fan[3].length; i++) {
				row += fan[3][i];
				if (i < fan[3].length - 1) {
					row += ", ";
				}
			}
			row += ')';
		}
		row += '</div>';
		row += '</td>';
		$(tableSelector).append(row);
	}
}

// Create an artist link using special Pandora tag syntax, using the currently
// selected text as an artist name (if any) and prompting for any other information.
function createArtistLink(textAreaSelector) {
	var element = $(textAreaSelector).get(0);
	var artist = getSelectionValue(element);
	var context = createModalContext(element);
	if (artist == "") {
		showModalDialog("Please enter the name of the artist:", createArtistLinkFromText, restoreSelection, context);
	} else {
		createArtistLinkFromText(artist, context);
	}
}

// Create an artist link in the text selection for the specified artist
function createArtistLinkFromText(artist, context) {
	if (artist && artist.length > 0) {
		replaceSelectionWith(context.element, "[artist]" + artist + "[/artist]", context.range);
	} else {
		restoreSelection();
	}
}

// Create a profile link using special Pandora tag syntax, using the currently
// selected text as a profile name (if any) and prompting for any other information.
function createProfileLink(textAreaSelector) {
	var element = $(textAreaSelector).get(0);
	var user = getSelectionValue(element);
	var context = createModalContext(element);
	if (user == "") {
		showModalDialog("Please enter the email address or profile URL of the Pandora user:", createProfileLinkFromText, restoreSelection, context);
		//showModalDialog("Please enter the email address or profile URL of the Pandora user.", createProfileLinkFromText, restoreSelection, context);
	} else {
		createProfileLinkFromText(user, context);
	}
}

// Create a profile link in the text selection for the specified user
function createProfileLinkFromText(user, context) {
	if (user && user.length > 0) {
		var re = new RegExp("/people/(.*)");
		var match = re.exec(user);
		if (match && match.length > 1) {
			user = match[1];
		}
		replaceSelectionWith(context.element, "[profile]" + user + "[/profile]", context.range);
	} else {
		restoreSelection();
	}
}

// Create an album link using special Pandora tag syntax, using the currently
// selected text as an album name (if any) and prompting for any other information.
function createAlbumLink(textAreaSelector) {
	var element = $(textAreaSelector).get(0);
	var album = getSelectionValue(element);
	var context = createModalContext(element);
	if (album == "") {
		showModalDialog("Please enter the name of the album:", createAlbumLinkFromText, restoreSelection, context);
	} else {
		context.album = album;
		showModalDialog("To make sure we have the correct '" + album + "', please tell us the artist:", createAlbumLinkFromText, restoreSelection, context);
	}
}

// Create an album link in the text selection with the specified text
function createAlbumLinkFromText(text, context) {
	if (text && text.length > 0) {
		if (context.album == null) {
			// We only have album, get artist with another dialog
			context.album = text;
			showModalDialog("To make sure we have the correct '" + text + "', please tell us the artist:", createAlbumLinkFromText, restoreSelection, context);
		} else {
			// We have album and artist
			replaceSelectionWith(context.element, '[album artist="' + text + '"]' + context.album + "[/album]", context.range);
		}

	} else {
		restoreSelection();
	}
}

// Create a song link using special Pandora tag syntax, using the currently
// selected text as a song name (if any) and prompting for any other information.
function createSongLink(textAreaSelector) {
	var element = $(textAreaSelector).get(0);
	var song = getSelectionValue(element);
	var context = createModalContext(element);
	if (song == "") {
		showModalDialog("Please enter the name of the song:", createSongLinkFromText, restoreSelection, context);
	} else {
		context.song = song;
		showModalDialog("To make sure we have the correct '" + song + "', please tell us the artist:", createSongLinkFromText, restoreSelection, context);
	}
}

// Create a song link in the text selection with the specified text
function createSongLinkFromText(text, context) {
	if (text && text.length > 0) {
		if (context.song == null) {
			// We only have song, get artist with another dialog
			context.song = text;
			showModalDialog("To make sure we have the correct '" + text + "', please tell us the artist:", createSongLinkFromText, restoreSelection, context);
		} else {
			// We have song and artist
			replaceSelectionWith(context.element, '[song artist="' + text + '"]' + context.song + "[/song]", context.range);
		}
	} else {
		restoreSelection();
	}
}

// Create a context object to use with the modal dialog callbacks
function createModalContext(element) {
	var context = { element : element };
	if (document.selection) {
		// Save range in context because modal dialog wipes it out somehow (IE only)
		element.focus();
		range = document.selection.createRange();
		context.range = range;
	}
	return context;
}

// Restore the text selection's cursor position for the given modal context
function restoreSelection(context) {
	if (context.range != null) {	// IE only
		var dummyRange = context.range.duplicate();
		dummyRange.moveToElementText(context.element);
		dummyRange.setEndPoint("EndToEnd", context.range);
		var start = dummyRange.text.length - context.range.text.length;
		if (context.range.parentElement() == context.element) {
			var finalRange = context.element.createTextRange();
			finalRange.move("character", start);
			finalRange.select();
		}
	}
}

// Get the value of the currently-selected text for the element (i.e. textarea)
// Returns the text as a string, or empty string if nothing is selected.
function getSelectionValue(element) {
	element.focus();
	if (element.selectionStart >= 0) {
		var start = element.selectionStart;
		var end = element.selectionEnd;
		return element.value.substring(start, end);
	} else if (document.selection) {
		element.focus();
		var range = document.selection.createRange();
		if (range.parentElement() == element) {
			return range.text;
		}
	}
	return "";
}

// Replace the current text selection with the supplied text.  If nothing is selected,
// the new text will be inserted at the current cursor position.
function replaceSelectionWith(element, text, range) {
	element.focus();
	if (element.selectionStart >= 0) {			// Mozilla, Firefox, Safari and Opera
		var start = element.selectionStart;
		var end = element.selectionEnd;
		var value = element.value;
		element.value = value.substr(0, start) + text + value.substr(end, value.length);
		element.focus();
		element.setSelectionRange(start + text.length, start + text.length);

	} else if (document.selection) {			// Internet Explorer
		if (range == null) {
			range = document.selection.createRange();
		}
		var dummyRange = range.duplicate();
		dummyRange.moveToElementText(element);
		dummyRange.setEndPoint("EndToEnd", range);
		var start = dummyRange.text.length - range.text.length;

		// If selection range is not in the expected element, abort
		if (range.parentElement() != element) {
			return false;
		}

		// Set the range with the new text
		range.text = text;

		// Set carat to end of selection
		var finalRange = element.createTextRange();
		finalRange.move("character", start + text.length);
		finalRange.select();

	} else {
		// Fallback - just append it to the end
		element.value += text;
	}

	// Restore focus to element (only works reliably using setTimeout)
	setTimeout(function() {
		element.focus();
	}, 1);

}

// Escape HTML entities in a string
function escapeHtml(html) {
	html = html.replace(/&/g, '&amp;');
	html = html.replace(/</g, '&lt;');
	html = html.replace(/>/g, '&gt;');
	html = html.replace(/"/g, '&quot;');
	return html;
}

// Decode a URL-encoded string
function urlDecode(str) {
	return unescape(str.replace(/\+/g, " "));
}

// Prevent dragging of links into textarea (see RADIO-4066)
function preventDragDrop(selector) {
	$(selector).each(function() {
		if (this.addEventListener) {
			this.addEventListener("dragdrop", function(e) {
				e.stopPropagation();
				e.preventDefault();
			}, true);
		}
	});
}

// Display an error message in a floating div, with a dismiss button
function showErrorMessage(msg) {
	if (document.createElement) {
		msg = msg.replace(/\n/g, "<br>");
		var div = document.createElement("div");
		$(div).addClass("error").html(msg + "<br><br>");
		var img = document.createElement("img");
		img.src = "/images/continue_button.gif";
		img.width = 100;
		img.height = 22;
		img.style.cursor = "pointer";
		$(div).append(img);
		$(img).mouseover(function() {
			this.src = "/images/continue_button_hover.gif";
		});
		$(img).mouseout(function() {
			this.src = "/images/continue_button.gif";
		});
		$(img).click(function() {
			$(div).hide();
		})
		$(document.body).append(div);
	} else {
		alert(msg);
	}
}

var gModalDialogSubmitCallback = null;
var gModalDialogCancelCallback = null;
var gModalDialogContext = null;

// Show a modal dialog that simulates a javascript prompt() call.  If the user hits
// ENTER or submits the dialog, the submitCallback is called with the user's text and
// context as arguments.  Otherwise the cancelCallback (if any) is called with the context.
function showModalDialog(text, submitCallback, cancelCallback, context) {
	gModalDialogSubmitCallback = submitCallback;
	gModalDialogCancelCallback = cancelCallback;
	gModalDialogContext = context;
	$('#modalDialog').modalContent({opacity: '.40', background: '#fff'}, "fadeIn", "slow", function() {
		$('#modalContent input').get(0).focus();
	});
	$('#modalContent span').html(text);
}

// Internal function used by the modal dialog box
function modalDialogSubmit() {
	var text = $('#modalContent input').val();
	$('#modalDialog').unmodalContent();
	if (gModalDialogSubmitCallback) {
		var tmpCallback = gModalDialogSubmitCallback;
		var tmpContext = gModalDialogContext;
		setTimeout(function() {
			tmpCallback(text, tmpContext);
		}, 1);
	}
	gModalDialogSubmitCallback = null;
	gModalDialogCancelCallback = null;
	gModalDialogContext = null;
}

// Internal function used by the modal dialog box
function modalDialogCancel() {
	$('#modalDialog').unmodalContent();
	if (gModalDialogCancelCallback) {
		var tmpCallback = gModalDialogCancelCallback;
		var tmpContext = gModalDialogContext;
		setTimeout(function() {
			tmpCallback(tmpContext);
		}, 1);
	}
	gModalDialogSubmitCallback = null;
	gModalDialogCancelCallback = null;
	gModalDialogContext = null;
}

function reloadWindow() {
	if ($.browser.safari
			&& window.location.hash != null
			&& window.location.hash.length > 0) {
		// Safari will not reload the window if there is a hash value in the url
		// if the url has a hash value and we are in safari, reconstruct the url
		// without the hash.  Also, safari requires that we tack a unique query string onto the url
		var url = window.location.protocol + "//" + window.location.host + window.location.pathname + "?deleteHash=" + new Date().getTime();
		window.location.href = url;
	} else {
		window.location.reload();
	}
}

function getKeyCode(event) {
	if (event.which) {
		return event.which;
	} else {
		return event.keyCode;
	}
}

function executeClickEvent(jqueryAnchor) {
	var onClick = jqueryAnchor.attr("onclick");
	var strHref = jqueryAnchor.attr("href");

	if ($.browser.msie) {
		//TODO:  get rid of these IE hacks when we upgrade jquery (RADIO-4172)
		if (typeof(onClick) == "function") {
			//the anchor click event fails on ie6, so call the function a bit more manually
			onClick.call();
		} else if (strHref != null && strHref.length > 0) {
			//Marc is really going to like this one...
			if (strHref.indexOf("javascript:") == 0) {
				eval(strHref.substring("javascript:".length));
			} else {
				window.location.href = strHref;
			}
		}
	} else {
		if (onClick != null && onClick.length > 0) {
			jqueryAnchor.click();
		} else if (strHref != null && strHref.length > 0) {
			window.location.href = strHref;
		}
	}
}


