/*
    http://www.JSON.org/json2.js
    2009-08-17

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the value

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/



// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    this.JSON = {};
}

(function () {
    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return isFinite(this.valueOf()) ?
                   this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z' : null;
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    // JGA { 12/23/2009
    // IE defines its own JSON.stringify method but its broken for properties == '',
    // IE converts empty strings to null

    //if (typeof JSON.stringify !== 'function') {

    // } JGA
    
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    // JGA { 12/23/2009
    // IE defines its own JSON.stringify method but its broken for properties == '',
    // IE converts empty strings to null
        
    //}

    // } JGA


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
}());
/*
 * jQuery JSONP Core Plugin 2.1.4 (2010-11-17)
 * 
 * http://code.google.com/p/jquery-jsonp/
 *
 * Copyright (c) 2010 Julian Aubourg
 *
 * This document is licensed as free software under the terms of the
 * MIT License: http://www.opensource.org/licenses/mit-license.php
 */
( function( $ , setTimeout ) {
	
	// ###################### UTILITIES ##
	
	// Noop
	function noop() {
	}
	
	// Generic callback
	function genericCallback( data ) {
		lastValue = [ data ];
	}

	// Add script to document
	function appendScript( node ) {
		head.insertBefore( node , head.firstChild );
	}
	
	// Call if defined
	function callIfDefined( method , object , parameters ) {
		return method && method.apply( object.context || object , parameters );
	}
	
	// Give joining character given url
	function qMarkOrAmp( url ) {
		return /\?/ .test( url ) ? "&" : "?";
	}
	
	var // String constants (for better minification)
		STR_ASYNC = "async",
		STR_CHARSET = "charset",
		STR_EMPTY = "",
		STR_ERROR = "error",
		STR_JQUERY_JSONP = "_jqjsp",
		STR_ON = "on",
		STR_ONCLICK = STR_ON + "click",
		STR_ONERROR = STR_ON + STR_ERROR,
		STR_ONLOAD = STR_ON + "load",
		STR_ONREADYSTATECHANGE = STR_ON + "readystatechange",
		STR_REMOVE_CHILD = "removeChild",
		STR_SCRIPT_TAG = "<script/>",
		STR_SUCCESS = "success",
		STR_TIMEOUT = "timeout",
		
		// Shortcut to jQuery.browser
		browser = $.browser,
		
		// Head element (for faster use)
		head = $( "head" )[ 0 ] || document.documentElement,
		// Page cache
		pageCache = {},
		// Counter
		count = 0,
		// Last returned value
		lastValue,
		
		// ###################### DEFAULT OPTIONS ##
		xOptionsDefaults = {
			//beforeSend: undefined,
			//cache: false,
			callback: STR_JQUERY_JSONP,
			//callbackParameter: undefined,
			//charset: undefined,
			//complete: undefined,
			//context: undefined,
			//data: "",
			//dataFilter: undefined,
			//error: undefined,
			//pageCache: false,
			//success: undefined,
			//timeout: 0,
			//traditional: false,		
			url: location.href
		};
	
	// ###################### MAIN FUNCTION ##
	function jsonp( xOptions ) {
		
		// Build data with default
		xOptions = $.extend( {} , xOptionsDefaults , xOptions );
		
		// References to xOptions members (for better minification)
		var completeCallback = xOptions.complete,
			dataFilter = xOptions.dataFilter,
			callbackParameter = xOptions.callbackParameter,
			successCallbackName = xOptions.callback,
			cacheFlag = xOptions.cache,
			pageCacheFlag = xOptions.pageCache,
			charset = xOptions.charset,
			url = xOptions.url,
			data = xOptions.data,
			timeout = xOptions.timeout,
			pageCached,
			
			// Abort/done flag
			done = 0,
			
			// Life-cycle functions
			cleanUp = noop;
		
		// Create the abort method
		xOptions.abort = function() { 
			! done++ &&	cleanUp(); 
		};

		// Call beforeSend if provided (early abort if false returned)
		if ( callIfDefined( xOptions.beforeSend, xOptions , [ xOptions ] ) === false || done ) {
			return xOptions;
		}
			
		// Control entries
		url = url || STR_EMPTY;
		data = data ? ( (typeof data) == "string" ? data : $.param( data , xOptions.traditional ) ) : STR_EMPTY;
			
		// Build final url
		url += data ? ( qMarkOrAmp( url ) + data ) : STR_EMPTY;
		
		// Add callback parameter if provided as option
		callbackParameter && ( url += qMarkOrAmp( url ) + encodeURIComponent( callbackParameter ) + "=?" );
		
		// Add anticache parameter if needed
		! cacheFlag && ! pageCacheFlag && ( url += qMarkOrAmp( url ) + "_" + ( new Date() ).getTime() + "=" );
		
		// Replace last ? by callback parameter
		url = url.replace( /=\?(&|$)/ , "=" + successCallbackName + "$1" );
		
		// Success notifier
		function notifySuccess( json ) {
			! done++ && setTimeout( function() {
				cleanUp();
				// Pagecache if needed
				pageCacheFlag && ( pageCache [ url ] = { s: [ json ] } );
				// Apply the data filter if provided
				dataFilter && ( json = dataFilter.apply( xOptions , [ json ] ) );
				// Call success then complete
				callIfDefined( xOptions.success , xOptions , [ json , STR_SUCCESS ] );
				callIfDefined( completeCallback , xOptions , [ xOptions , STR_SUCCESS ] );
			} , 0 );
		}
		
		// Error notifier
		function notifyError( type ) {
			! done++ && setTimeout( function() {
				// Clean up
				cleanUp();
				// If pure error (not timeout), cache if needed
				pageCacheFlag && type != STR_TIMEOUT && ( pageCache[ url ] = type );
				// Call error then complete
				callIfDefined( xOptions.error , xOptions , [ xOptions , type ] );
				callIfDefined( completeCallback , xOptions , [ xOptions , type ] );
			} , 0 );
		}
	    
		// Check page cache
		pageCacheFlag && ( pageCached = pageCache[ url ] ) 
			? ( pageCached.s ? notifySuccess( pageCached.s[ 0 ] ) : notifyError( pageCached ) )
			:
			// Initiate request
			setTimeout( function( script , scriptAfter , timeoutTimer ) {
				
				if ( ! done ) {
				
					// If a timeout is needed, install it
					timeoutTimer = timeout > 0 && setTimeout( function() {
						notifyError( STR_TIMEOUT );
					} , timeout );
					
					// Re-declare cleanUp function
					cleanUp = function() {
						timeoutTimer && clearTimeout( timeoutTimer );
						script[ STR_ONREADYSTATECHANGE ]
							= script[ STR_ONCLICK ]
							= script[ STR_ONLOAD ]
							= script[ STR_ONERROR ]
							= null;
						head[ STR_REMOVE_CHILD ]( script );
						scriptAfter && head[ STR_REMOVE_CHILD ]( scriptAfter );
					};
					
					// Install the generic callback
					// (BEWARE: global namespace pollution ahoy)
					window[ successCallbackName ] = genericCallback;

					// Create the script tag
					script = $( STR_SCRIPT_TAG )[ 0 ];
					script.id = STR_JQUERY_JSONP + count++;
					
					// Set charset if provided
					if ( charset ) {
						script[ STR_CHARSET ] = charset;
					}
					
					// Callback function
					function callback( result ) {
						( script[ STR_ONCLICK ] || noop )();
						result = lastValue;
						lastValue = undefined;
						result ? notifySuccess( result[ 0 ] ) : notifyError( STR_ERROR );
					}
										
					// IE: event/htmlFor/onclick trick
					// One can't rely on proper order for onreadystatechange
					// We have to sniff since FF doesn't like event & htmlFor... at all
					if ( browser.msie ) {
						
						script.event = STR_ONCLICK;
						script.htmlFor = script.id;
						script[ STR_ONREADYSTATECHANGE ] = function() {
							/loaded|complete/.test( script.readyState ) && callback();
						};
						
					// All others: standard handlers
					} else {					
					
						script[ STR_ONERROR ] = script[ STR_ONLOAD ] = callback;
						
						browser.opera ?
							
							// Opera: onerror is not called, use synchronized script execution
							( ( scriptAfter = $( STR_SCRIPT_TAG )[ 0 ] ).text = "jQuery('#" + script.id + "')[0]." + STR_ONERROR + "()" )
							
							// Firefox: set script as async to avoid blocking scripts (3.6+ only)
							: script[ STR_ASYNC ] = STR_ASYNC;
							
						;
					}
					
					// Set source
					script.src = url;
					
					// Append main script
					appendScript( script );
					
					// Opera: Append trailing script
					scriptAfter && appendScript( scriptAfter );
				}
				
			} , 0 );
		
		return xOptions;
	}
	
	// ###################### SETUP FUNCTION ##
	jsonp.setup = function( xOptions ) {
		$.extend( xOptionsDefaults , xOptions );
	};

	// ###################### INSTALL in jQuery ##
	$.jsonp = jsonp;
	
} )( jQuery , setTimeout );/**
 * Copyright 2007 Tim Down.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS"http://www.healthcare.gov/jscripts/BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/**
 * simpledateformat.js
 *
 * A faithful JavaScript implementation of Java's SimpleDateFormat's format
 * method. All pattern layouts present in the Java implementation are
 * implemented here except for z, the text version of the date'http://www.cuidadodesalud.gov/enes/jscripts/s time zone.
 *
 * Thanks to Ash Searle (http://hexmen.com/blog/) for his fix to my
 * misinterpretation of pattern letters h and k.
 * 
 * See the official Sun documentation for the Java version:
 * http://java.sun.com/j2se/1.5.0/docs/api/java/text/SimpleDateFormat.html
 *
 * Author: Tim Down <tim@timdown.co.uk>
 * Last modified: 6/2/2007
 * Website: http://www.timdown.co.uk/code/simpledateformat.php
 */
 
/* ------------------------------------------------------------------------- */

var SimpleDateFormat;

(function() {
	function isUndefined(obj) {
		return typeof obj == "undefined";
	}

	var regex = /('[^']*')|(G+|y+|M+|w+|W+|D+|d+|F+|E+|a+|H+|k+|K+|h+|m+|s+|S+|Z+)|([a-zA-Z]+)|([^a-zA-Z']+)/;
	var monthNames = ["January", "February", "March", "April", "May", "June",
		"July", "August", "September", "October", "November", "December"];
	var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
	var TEXT2 = 0, TEXT3 = 1, NUMBER = 2, YEAR = 3, MONTH = 4, TIMEZONE = 5;
	var types = {
		G : TEXT2,
		y : YEAR,
		M : MONTH,
		w : NUMBER,
		W : NUMBER,
		D : NUMBER,
		d : NUMBER,
		F : NUMBER,
		E : TEXT3,
		a : TEXT2,
		H : NUMBER,
		k : NUMBER,
		K : NUMBER,
		h : NUMBER,
		m : NUMBER,
		s : NUMBER,
		S : NUMBER,
		Z : TIMEZONE
	};
	var ONE_DAY = 24 * 60 * 60 * 1000;
	var ONE_WEEK = 7 * ONE_DAY;
	var DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK = 1;

	var newDateAtMidnight = function(year, month, day) {
		var d = new Date(year, month, day, 0, 0, 0);
		d.setMilliseconds(0);
		return d;
	}

	Date.prototype.getDifference = function(date) {
		return this.getTime() - date.getTime();
	};

	Date.prototype.isBefore = function(d) {
		return this.getTime() < d.getTime();
	};

	Date.prototype.getUTCTime = function() {
		return Date.UTC(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(),
				this.getSeconds(), this.getMilliseconds());
	};

	Date.prototype.getTimeSince = function(d) {
		return this.getUTCTime() - d.getUTCTime();
	};

	Date.prototype.getPreviousSunday = function() {
		// Using midday avoids any possibility of DST messing things up
		var midday = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 12, 0, 0);
		var previousSunday = new Date(midday.getTime() - this.getDay() * ONE_DAY);
		return newDateAtMidnight(previousSunday.getFullYear(), previousSunday.getMonth(),
				previousSunday.getDate());
	}

	Date.prototype.getWeekInYear = function(minimalDaysInFirstWeek) {
		if (isUndefined(this.minimalDaysInFirstWeek)) {
			minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
		}
		var previousSunday = this.getPreviousSunday();
		var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
		var numberOfSundays = previousSunday.isBefore(startOfYear) ?
			0 : 1 + Math.floor(previousSunday.getTimeSince(startOfYear) / ONE_WEEK);
		var numberOfDaysInFirstWeek =  7 - startOfYear.getDay();
		var weekInYear = numberOfSundays;
		if (numberOfDaysInFirstWeek < minimalDaysInFirstWeek) {
			weekInYear--;
		}
		return weekInYear;
	};

	Date.prototype.getWeekInMonth = function(minimalDaysInFirstWeek) {
		if (isUndefined(this.minimalDaysInFirstWeek)) {
			minimalDaysInFirstWeek = DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK;
		}
		var previousSunday = this.getPreviousSunday();
		var startOfMonth = newDateAtMidnight(this.getFullYear(), this.getMonth(), 1);
		var numberOfSundays = previousSunday.isBefore(startOfMonth) ?
			0 : 1 + Math.floor((previousSunday.getTimeSince(startOfMonth)) / ONE_WEEK);
		var numberOfDaysInFirstWeek =  7 - startOfMonth.getDay();
		var weekInMonth = numberOfSundays;
		if (numberOfDaysInFirstWeek >= minimalDaysInFirstWeek) {
			weekInMonth++;
		}
		return weekInMonth;
	};

	Date.prototype.getDayInYear = function() {
		var startOfYear = newDateAtMidnight(this.getFullYear(), 0, 1);
		return 1 + Math.floor(this.getTimeSince(startOfYear) / ONE_DAY);
	};

	/* ----------------------------------------------------------------- */

	SimpleDateFormat = function(formatString) {
		this.formatString = formatString;
	};

	/**
	 * Sets the minimum number of days in a week in order for that week to
	 * be considered as belonging to a particular month or year
	 */
	SimpleDateFormat.prototype.setMinimalDaysInFirstWeek = function(days) {
		this.minimalDaysInFirstWeek = days;
	};

	SimpleDateFormat.prototype.getMinimalDaysInFirstWeek = function(days) {
		return isUndefined(this.minimalDaysInFirstWeek)	?
			DEFAULT_MINIMAL_DAYS_IN_FIRST_WEEK : this.minimalDaysInFirstWeek;
	};

	SimpleDateFormat.prototype.format = function(date) {
		var formattedString = "";
		var result;

		var padWithZeroes = function(str, len) {
			while (str.length < len) {
				str = "0" + str;
			}
			return str;
		};

		var formatText = function(data, numberOfLetters, minLength) {
			return (numberOfLetters >= 4) ? data : data.substr(0, Math.max(minLength, numberOfLetters));
		};

		var formatNumber = function(data, numberOfLetters) {
			var dataString = "" + data;
			// Pad with 0s as necessary
			return padWithZeroes(dataString, numberOfLetters);
		};

		var searchString = this.formatString;
		while ((result = regex.exec(searchString))) {
			var matchedString = result[0];
			var quotedString = result[1];
			var patternLetters = result[2];
			var otherLetters = result[3];
			var otherCharacters = result[4];

			// If the pattern matched is quoted string, output the text between the quotes
			if (quotedString) {
				if (quotedString == "''") {
					formattedString += "'";
				} else {
					formattedString += quotedString.substring(1, quotedString.length - 1);
				}
			} else if (otherLetters) {
				// Swallow non-pattern letters by doing nothing here
			} else if (otherCharacters) {
				// Simply output other characters
				formattedString += otherCharacters;
			} else if (patternLetters) {
				// Replace pattern letters
				var patternLetter = patternLetters.charAt(0);
				var numberOfLetters = patternLetters.length;
				var rawData = "";
				switch (patternLetter) {
					case "G":
						rawData = "AD";
						break;
					case "y":
						rawData = date.getFullYear();
						break;
					case "M":
						rawData = date.getMonth();
						break;
					case "w":
						rawData = date.getWeekInYear(this.getMinimalDaysInFirstWeek());
						break;
					case "W":
						rawData = date.getWeekInMonth(this.getMinimalDaysInFirstWeek());
						break;
					case "D":
						rawData = date.getDayInYear();
						break;
					case "d":
						rawData = date.getDate();
						break;
					case "F":
						rawData = 1 + Math.floor((date.getDate() - 1) / 7);
						break;
					case "E":
						rawData = dayNames[date.getDay()];
						break;
					case "a":
						rawData = (date.getHours() >= 12) ? "PM" : "AM";
						break;
					case "H":
						rawData = date.getHours();
						break;
					case "k":
						rawData = date.getHours() || 24;
						break;
					case "K":
						rawData = date.getHours() % 12;
						break;
					case "h":
						rawData = (date.getHours() % 12) || 12;
						break;
					case "m":
						rawData = date.getMinutes();
						break;
					case "s":
						rawData = date.getSeconds();
						break;
					case "S":
						rawData = date.getMilliseconds();
						break;
					case "Z":
						rawData = date.getTimezoneOffset(); // This is returns the number of minutes since GMT was this time.
						break;
				}
				// Format the raw data depending on the type
				switch (types[patternLetter]) {
					case TEXT2:
						formattedString += formatText(rawData, numberOfLetters, 2);
						break;
					case TEXT3:
						formattedString += formatText(rawData, numberOfLetters, 3);
						break;
					case NUMBER:
						formattedString += formatNumber(rawData, numberOfLetters);
						break;
					case YEAR:
						if (numberOfLetters <= 3) {
							// Output a 2-digit year
							var dataString = "" + rawData;
							formattedString += dataString.substr(2, 2);
						} else {
							formattedString += formatNumber(rawData, numberOfLetters);
						}
						break;
					case MONTH:
						if (numberOfLetters >= 3) {
							formattedString += formatText(monthNames[rawData], numberOfLetters, numberOfLetters);
						} else {
							// NB. Months returned by getMonth are zero-based
							formattedString += formatNumber(rawData + 1, numberOfLetters);
						}
						break;
					case TIMEZONE:
						var isPositive = (rawData > 0);
						// The following line looks like a mistake but isn't
						// because of the way getTimezoneOffset measures.
						var prefix = isPositive ? "-" : "+";
						var absData = Math.abs(rawData);

						// Hours
						var hours = "" + Math.floor(absData / 60);
						hours = padWithZeroes(hours, 2);
						// Minutes
						var minutes = "" + (absData % 60);
						minutes = padWithZeroes(minutes, 2);

						formattedString += prefix + hours + minutes;
						break;
				}
			}
			searchString = searchString.substr(result.index + result[0].length);
		}
		return formattedString;
	};
})();/*
 * timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
 * @requires jQuery v1.2.3 or later
 *
 * Timeago is a jQuery plugin that makes it easy to support automatically
 * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
 *
 * For usage and examples, visit:
 * http://timeago.yarp.com/
 *
 * Licensed under the MIT:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
 */
(function($) {
  $.timeago = function(timestamp) {
    if (timestamp instanceof Date) {
      return inWords(timestamp);
    } else if (typeof timestamp === "string") {
      return inWords($.timeago.parse(timestamp));
    } else {
      return inWords($.timeago.datetime(timestamp));
    }
  };
  var $t = $.timeago;

  $.extend($.timeago, {
    settings: {
      refreshMillis: 60000,
      allowFuture: false,
      strings: {
        prefixAgo: null,
        prefixFromNow: null,
        suffixAgo: "ago",
        suffixFromNow: "from now",
        seconds: "less than a minute",
        minute: "about a minute",
        minutes: "%d minutes",
        hour: "about an hour",
        hours: "about %d hours",
        day: "a day",
        days: "%d days",
        month: "about a month",
        months: "%d months",
        year: "about a year",
        years: "%d years",
        numbers: []
      }
    },
    inWords: function(distanceMillis) {
      var $l = this.settings.strings;
      var prefix = $l.prefixAgo;
      var suffix = $l.suffixAgo;
      if (this.settings.allowFuture) {
        if (distanceMillis < 0) {
          prefix = $l.prefixFromNow;
          suffix = $l.suffixFromNow;
        }
        distanceMillis = Math.abs(distanceMillis);
      }

      var seconds = distanceMillis / 1000;
      var minutes = seconds / 60;
      var hours = minutes / 60;
      var days = hours / 24;
      var years = days / 365;

      function substitute(stringOrFunction, number) {
        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
        var value = ($l.numbers && $l.numbers[number]) || number;
        return string.replace(/%d/i, value);
      }

      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
        seconds < 90 && substitute($l.minute, 1) ||
        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
        minutes < 90 && substitute($l.hour, 1) ||
        hours < 24 && substitute($l.hours, Math.round(hours)) ||
        hours < 48 && substitute($l.day, 1) ||
        days < 30 && substitute($l.days, Math.floor(days)) ||
        days < 60 && substitute($l.month, 1) ||
        days < 365 && substitute($l.months, Math.floor(days / 30)) ||
        years < 2 && substitute($l.year, 1) ||
        substitute($l.years, Math.floor(years));

      return $.trim([prefix, words, suffix].join(" "));
    },
    parse: function(iso8601) {
      var s = $.trim(iso8601);
      s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
      s = s.replace(/-/,"/").replace(/-/,"/");
      s = s.replace(/T/," ").replace(/Z/," UTC");
      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
      return new Date(s);
    },
    datetime: function(elem) {
      // jQuery's `is()` doesn't play well with HTML5 in IE
      var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
      var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
      return $t.parse(iso8601);
    }
  });

  $.fn.timeago = function() {
    var self = this;
    self.each(refresh);

    var $s = $t.settings;
    if ($s.refreshMillis > 0) {
      setInterval(function() { self.each(refresh); }, $s.refreshMillis);
    }
    return self;
  };

  function refresh() {
    var data = prepareData(this);
    if (!isNaN(data.datetime)) {
      $(this).text(inWords(data.datetime));
    }
    return this;
  }

  function prepareData(element) {
    element = $(element);
    if (!element.data("timeago")) {
      element.data("timeago", { datetime: $t.datetime(element) });
      var text = $.trim(element.text());
      if (text.length > 0) {
        element.attr("title", text);
      }
    }
    return element.data("timeago");
  }

  function inWords(date) {
    return $t.inWords(distance(date));
  }

  function distance(date) {
    return (new Date().getTime() - date.getTime());
  }

  // fix for IE6 suckage
  document.createElement("abbr");
  document.createElement("time");
}(jQuery));
/**
 * Utility functions and constants for use with client services.
 */
(function($){

     /**
      * Constant indicating a successful request.
      */           
     var STATUS_SUCCESS = "success";
     /**
      * Constant indicating an unsuccessful request.
      */ 
     var STATUS_ERROR = "error";
     /**
      * Constant for a DELETE request.
      */            
     var TYPE_DELETE = "DELETE";
     /**
      * Constant for a GET request.
      */ 
     var TYPE_GET = "GET";
     /**
      * Constant for a POST request.
      */ 
     var TYPE_POST = "POST";
     /**
      * Constant for a PUT request.
      */ 
     var TYPE_PUT = "PUT";
     
   
   $.PercServiceUtils =  {
      STATUS_ERROR: STATUS_ERROR,
      STATUS_SUCCESS: STATUS_SUCCESS,
      TYPE_DELETE: TYPE_DELETE,
      TYPE_GET: TYPE_GET,
      TYPE_POST: TYPE_POST,
      TYPE_PUT: TYPE_PUT,
      makeJsonRequest : makeJsonRequest,
      makeXmlRequest : makeXmlRequest,
      makeRequest : makeRequest,
      makeDeleteRequest : makeDeleteRequest,
      extractDefaultErrorMessage : extractDefaultErrorMessage,         
      extractFieldErrorCode : extractFieldErrorCode         
   };
    $.ajaxSetup({
        timeout: 60000
    });
     
     
     /**
      *  Makes a request to a specified url, returning status and results
      *  in the passed in callback function. Uses JSON as the content type
      *  for passed in data and results.
      *        
      *  @param url {String} the url string where the request will be made. Cannot
      *  be <code>null</code> or empty.
      *  @param type {String} the type of request to be made, one of the following:
      *  (DELETE, GET, POST, PUT).
      *  @param sync {boolean} flag indicating that a syncronous call should be made
      *  if set to <code>true</code>. It is recommended to try to keep this set
      *  to <code>false</code> as synchronous calls will freeze the UI until complete.
      *  @param callback {function} the callback function that will return status and
      *  request results. 
      *  <pre>  
      *  The callback arguments will be:
      *     <table>                    
      *      <tr><td>Arg</td><td>Desc</td></tr> 
      *      <tr><td>status</td><td>success or error</td></tr>           
      *      <tr><td>result</td><td>if successful then a object with the following
      *      properties {data, textstatus} else if unsuccessful then
      *      {request, textstatus, error}</td></tr> 
      *     </table>                 
      *  </pre>
      *  @param dataObject {Object} a javascript object that will be serialized into
      *  JSON for request. This is optional and is only needed if the request type
      *  is POST.                                                                                            
      */               
     function makeJsonRequest(url, type, sync, callback, dataObject){
        var self = this;
        var args = {
           dataType: 'json',
           async: !sync,             
             contentType: 'application/json',
             type: type, 
             url: url, 
             success: function(data, textstatus){
              var result = {
                 data: data,
                 textstatus: textstatus
              };
              callback(self.STATUS_SUCCESS, result);
            }, 
              error: function(request, textstatus, error){
               // look for status 204 which should not be an error
               if(request.status == 204 || request.status == 1223)
               {
                  var result = {
                     data: {},
                     textstatus: request.statusText
                  };
                  callback(self.STATUS_SUCCESS, result);
                  return;
               }
               var result = {
                  request: request,
                  textstatus: textstatus,
                  error: error
               };
               callback(self.STATUS_ERROR, result);
            }
        };
        if(dataObject)
        {
           $.extend(args, {data: JSON.stringify(dataObject)});
        }
        return $.ajax(args);
     }
    
    /**
     *  Makes generic request
     *  TODO: Write the other requests in terms of this one
     */
    function makeRequest(url, type, sync, callback, dataObject, contentType, dataType, noEscape){
        var self = this;
        var args = {
           dataType: dataType,
           async: !sync,             
             contentType: contentType,
             type: type,
             url: (noEscape) ? url : escape(url),
             success: function(data, textstatus){
              var result = {
                 data: data,
                 textstatus: textstatus
              };
              callback(self.STATUS_SUCCESS, result);
            }, 
              error: function(request, textstatus, error){
               // look for status 204 which should not be an error
               if(request.status == 204 || request.status == 1223)
               {
                  var result = {
                     data: {},
                     textstatus: request.statusText
                  };
                  callback(self.STATUS_SUCCESS, result);
                  return;
               }
               var result = {
                  request: request,
                  textstatus: textstatus,
                  error: error
               };
               callback(self.STATUS_ERROR, result);
            }
        };
        if(dataObject)
        {
           $.extend(args, {data: JSON.stringify(dataObject)});
        }
        $.ajax(args);
     }
     /**
      *  Makes a request to a specified url, returning status and results
      *  in the passed in callback function. Uses XML as the content type
      *  for passed in data and results.
      *        
      *  @param url {String} the url string where the request will be made. Cannot
      *  be <code>null</code> or empty.
      *  @param type {String} the type of request to be made, one of the following:
      *  (DELETE, GET, POST, PUT).
      *  @param sync {boolean} flag indicating that a syncronous call should be made
      *  if set to <code>true</code>. It is recommended to try to keep this set
      *  to <code>false</code> as synchronous calls will freeze the UI until complete.
      *  @param callback {function} the callback function that will return status and
      *  request results. 
      *  <pre>  
      *  The callback arguments will be:
      *     <table>                    
      *      <tr><td>Arg</td><td>Desc</td></tr> 
      *      <tr><td>status</td><td>success or error</td></tr>           
      *      <tr><td>result</td><td>if successful then a object with the following
      *      properties {data, textstatus} else if unsuccessful then
      *      {request, textstatus, error}</td></tr> 
      *     </table>                 
      *  </pre>
      *  @param dataString {String} Xml to be passed by the request.
      *  This is optional and is only needed if the request type is POST.                                                                                            
      */               
     function makeXmlRequest(url, type, sync, callback, dataString){
        var self = this;
        var args = {
           dataType: 'xml',
           async: !sync,             
             contentType: 'text/xml',
             type: type, 
             url: url, 
             success: function(data, textstatus){
              var result = {
                 data: data,
                 textstatus: textstatus
              };
              callback(self.STATUS_SUCCESS, result);
            }, 
              error: function(request, textstatus, error){
               var result = {
                  request: request,
                  textstatus: textstatus,
                  error: error
               };
               callback(self.STATUS_ERROR, result);
            }
        };
        if(dataString)
        {
           $.extend(args, {data: dataString});
        }
        $.ajax(args);
     }

     function makeDeleteRequest(url, sync, callback, dataString){
        var self = this;
        var args = {

           dataType: 'text',
           async: !sync,
             contentType: 'text/xml',
             type: 'DELETE', 
             url: url, 
             success: function(data, textstatus){
              var result = {
                 data: data,
                 textstatus: textstatus
              };
              callback(self.STATUS_SUCCESS, result);
            }, 
              error: function(request, textstatus, error){
               var result = {
                  request: request,
                  textstatus: textstatus,
                  error: error
               };
               callback(self.STATUS_ERROR, result);
            }
        };
        if(dataString)
        {
           $.extend(args, {data: dataString});
        }
        $.ajax(args);
     }

     
     /**
      * Extracts the default error message from the returned request object.
      * Currently this ONLY works for JSON requests.
      * TODO: Extract for XML.
      * TODO: Use TMX and I18N
      * @param request the request may contain the default error message.
      * @return the default error message. It may be blank if cannot extract
      * the default error message from the request.
      */                                   
     function extractDefaultErrorMessage(request)
     {
        var buff = "";
        if(request == null || request.responseText == null || request.responseText.length == 0)
            return "";
        var error = null;
        try
        {   
            error = JSON.parse(request.responseText);
            //IE handles this differently from all other browsers, of course
            if($.browser.msie && !error && typeof(request.responseText) == 'string' && request.responseText.length > 0)
            {
                return request.responseText;
            }
        }
        catch(e){}

        if(error != null && error.Errors)
        {
            var def = "";
            if(typeof(error.defaultMessage) != 'undefined')
            {
                def = error.defaultMessage;
            }
            else if(typeof(error.Errors.globalError.defaultMessage) != 'undefined')
            {
                def = error.Errors.globalError.defaultMessage;
            }
            else if(typeof(error.Errors.localizedMessage) != 'undefined')
            {
                def = error.Errors.localizedMessage;
            }
            else if(typeof(error.Errors.globalError.code) != 'undefined')
            {
                var prefix = request.status == 500 ? "Server Error: " : "";
                def = prefix + error.Errors.globalError.code;
            }
            buff += def; 
        }
        else if (error != null && error.ValidationErrors != undefined)
        {
            var verrors = error.ValidationErrors;
            if (verrors.fieldErrors != undefined)
            {
                buff += objectErrorToString(verrors.fieldErrors);
            }
            else if (verrors.globalErrors != undefined)
            {
                buff += objectErrorToString(verrors.globalErrors);
            }
            else if (verrors.globalError != undefined)
            {
                buff += objectErrorToString(verrors.globalError);
            }
        }
        
        //XML section. for the moment just parse validation errors
        var xmlResponse;
        if (typeof(request.responseText) != 'undefined' && request.responseText.indexOf('<?xml') != -1) {
            xmlResponse = $(request.responseText);
        }
        else if (typeof(request) == 'string' && request.indexOf('<?xml') != -1){
            xmlResponse = $(request);
        }
        if (typeof(xmlResponse) != 'undefined' && xmlResponse.is('ValidationErrors'))
        {
            if (xmlResponse.find('globalErrors').find('defaultMessage').text() != "")
            {
                buff += xmlResponse.find('globalErrors').find('defaultMessage').text();
            }
            else if (xmlResponse.find('globalError').find('defaultMessage').text() != "")
            {
                buff += xmlResponse.find('globalError').find('defaultMessage').text() != "";
            }
            else if(xmlResponse.next('defaultMessage'))
            {
                buff += xmlResponse.next('defaultMessage')[0].nextSibling.nodeValue;
            }
        }
        if (buff == ""){ buff = request.responseText;}
        return buff;    
      }
     
     /**
      * Converts object validation errors that are in the response
      * into a string. 
      * See com.percussion.share.validation.PSErrors.PSObjectError
      * @param objectErrors maybe null.
      * @return a string
      */
     function objectErrorToString(objectErrors) {
         objectErrors = $.makeArray(objectErrors);
         var buf = "";
         for(i = 0; i < objectErrors.length; i++) {
             var oe = objectErrors[i];
             if (oe.defaultMessage) {
                 buf += replaceMessageTokens(oe.defaultMessage, oe.arguments);
             }
             else if (oe.code) {
                 buf += oe.code;
             }
         }
         return buf;
     }

     /**
      * Akin to Java's MessageFormat where {i} is 
      * replaced with an argument.
      */
     function replaceMessageTokens(msg, args)
     {
         args = $.makeArray(args);
         var token = "";
         for(i = 0; i < args.length; i++)
         {
             token = new RegExp("\\{" + i + "\\}", "g");
             msg = msg.replace(token, args[i]);
         }
         return msg;
     }
    /**
     * Extracts the code of a field error validation from the specified request.
     * @param request the request may contain a field error validation.
     * @return the error code. It may be blank if the request does not contain
     * a field error validation. 
     */
    function extractFieldErrorCode(request)
    {
        var error = JSON.parse(request.responseText);
        if (error.ValidationErrors == undefined)
            return "";
            
        var verrors = error.ValidationErrors;
        if (verrors.fieldErrors != undefined)
            return verrors.fieldErrors.code;
        
        return "";
        
    }
})(jQuery);
/*!
 * jQuery BBQ: Back Button & Query Library - v1.1 - 1/9/2010
 * http://benalman.com/projects/jquery-bbq-plugin/
 * 
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Script: jQuery BBQ: Back Button & Query Library
//
// *Version: 1.1, Last updated: 1/9/2010*
// 
// Project Home - http://benalman.com/projects/jquery-bbq-plugin/
// GitHub       - http://github.com/cowboy/jquery-bbq/
// Source       - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
// (Minified)   - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (3.7kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
// 
// Basic AJAX     - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/
// Advanced AJAX  - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/
// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
// Deparam        - http://benalman.com/code/projects/jquery-bbq/examples/deparam/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4a2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-bbq/unit/
// 
// About: Release History
// 
// 1.1   - (1/9/2010) Broke out the jQuery BBQ event.special window.onhashchange
//         functionality into a separate plugin for users who want just the
//         basic event & back button support, without all the extra awesomeness
//         that BBQ provides. This plugin will be included as part of jQuery BBQ,
//         but also be available separately. See <jQuery hashchange event>
//         plugin for more information. Also added the $.bbq.removeState method
//         and added additional $.deparam examples.
// 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and
//         location.hash would report incorrectly if the hash contained the ?
//         character. Also $.param.querystring and $.param.fragment will no
//         longer parse params out of a URL that doesn't contain ? or #,
//         respectively.
// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused
//         a "This page contains both secure and nonsecure items." warning when
//         used on an https:// page.
// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8
//         Compatibility View" modes erroneously report that the browser
//         supports the native window.onhashchange event, a slightly more
//         robust test needed to be added.
// 1.0   - (10/2/2009) Initial release

(function($,window){
  '$:nomunge'; // Used by YUI compressor.
  
  // Some convenient shortcuts.
  var undefined,
    loc = window.location,
    aps = Array.prototype.slice,
    decode = decodeURIComponent,
    
    // Method / object references.
    jq_param = $.param,
    jq_param_fragment,
    jq_deparam,
    jq_deparam_fragment,
    jq_bbq = $.bbq = $.bbq || {},
    jq_bbq_pushState,
    jq_bbq_getState,
    jq_elemUrlAttr,
    jq_event_special = $.event.special,
    
    // Reused strings.
    str_hashchange = 'hashchange',
    str_querystring = 'querystring',
    str_fragment = 'fragment',
    str_elemUrlAttr = 'elemUrlAttr',
    str_href = 'href',
    str_src = 'src',
    
    // Reused RegExp.
    re_trim_querystring = /^.*\?|#.*$/g,
    re_trim_fragment = /^.*\#/,
    
    // Used by jQuery.elemUrlAttr.
    elemUrlAttr_cache = {};
  
  // A few commonly used bits, broken out to help reduce minified file size.
  
  function is_string( arg ) {
    return typeof arg === 'string';
  };
  
  // Why write the same function twice? Let's curry! Mmmm, curry..
  
  function curry( func ) {
    var args = aps.call( arguments, 1 );
    
    return function() {
      return func.apply( this, args.concat( aps.call( arguments ) ) );
    };
  };
  
  // Get location.hash (or what you'd expect location.hash to be) sans any
  // leading #. Thanks for making this necessary, Firefox!
  function get_fragment( url ) {
    return url.replace( /^[^#]*#?(.*)$/, '$1' );
  };
  
  // Get location.search (or what you'd expect location.search to be) sans any
  // leading #. Thanks for making this necessary, IE6!
  function get_querystring( url ) {
    return url.replace( /(?:^[^?#]*\?([^#]*).*$)?.*/, '$1' );
  };
  
  // Section: Param (to string)
  // 
  // Method: jQuery.param.querystring
  // 
  // Retrieve the query string from a URL or if no arguments are passed, the
  // current window.location.
  // 
  // Usage:
  // 
  // > jQuery.param.querystring( [ url ] );
  // 
  // Arguments:
  // 
  //  url - (String) A URL containing query string params to be parsed. If url
  //    is not passed, the current window.location is used.
  // 
  // Returns:
  // 
  //  (String) The parsed query string, with any leading "?" removed.
  //
  
  // Method: jQuery.param.querystring (build url)
  // 
  // Merge a URL, with or without pre-existing query string params, plus any
  // object, params string or URL containing query string params into a new URL.
  // 
  // Usage:
  // 
  // > jQuery.param.querystring( url, params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  url - (String) A valid URL for params to be merged into. This URL may
  //    contain a query string and/or fragment (hash).
  //  params - (String) A params string or URL containing query string params to
  //    be merged into url.
  //  params - (Object) A params object to be merged into url.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  // 
  //    * 0: params in the params argument will override any query string
  //         params in url.
  //    * 1: any query string params in url will override params in the params
  //         argument.
  //    * 2: params argument will completely replace any query string in url.
  // 
  // Returns:
  // 
  //  (String) Either a params string with urlencoded data or a URL with a
  //    urlencoded query string in the format 'a=b&c=d&e=f'.
  
  // Method: jQuery.param.fragment
  // 
  // Retrieve the fragment (hash) from a URL or if no arguments are passed, the
  // current window.location.
  // 
  // Usage:
  // 
  // > jQuery.param.fragment( [ url ] );
  // 
  // Arguments:
  // 
  //  url - (String) A URL containing fragment (hash) params to be parsed. If
  //    url is not passed, the current window.location is used.
  // 
  // Returns:
  // 
  //  (String) The parsed fragment (hash) string, with any leading "#" removed.
  
  // Method: jQuery.param.fragment (build url)
  // 
  // Merge a URL, with or without pre-existing fragment (hash) params, plus any
  // object, params string or URL containing fragment (hash) params into a new
  // URL.
  // 
  // Usage:
  // 
  // > jQuery.param.fragment( url, params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  url - (String) A valid URL for params to be merged into. This URL may
  //    contain a query string and/or fragment (hash).
  //  params - (String) A params string or URL containing fragment (hash) params
  //    to be merged into url.
  //  params - (Object) A params object to be merged into url.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  // 
  //    * 0: params in the params argument will override any fragment (hash)
  //         params in url.
  //    * 1: any fragment (hash) params in url will override params in the
  //         params argument.
  //    * 2: params argument will completely replace any query string in url.
  // 
  // Returns:
  // 
  //  (String) Either a params string with urlencoded data or a URL with a
  //    urlencoded fragment (hash) in the format 'a=b&c=d&e=f'.
  
  function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) {
    var result,
      qs,
      matches,
      url_params,
      hash;
    
    if ( params !== undefined ) {
      // Build URL by merging params into url string.
      
      // matches[1] = url part that precedes params, not including trailing ?/#
      // matches[2] = params, not including leading ?/#
      // matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
      matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ );
      
      // Get the hash if in 'querystring' mode, and it exists.
      hash = matches[3] || '';
      
      if ( merge_mode === 2 && is_string( params ) ) {
        // If merge_mode is 2 and params is a string, merge the fragment / query
        // string into the URL wholesale, without converting it into an object.
        qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' );
        
      } else {
        // Convert relevant params in url to object.
        url_params = jq_deparam( matches[2] );
        
        params = is_string( params )
          
          // Convert passed params string into object.
          ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params )
          
          // Passed params object.
          : params;
        
        qs = merge_mode === 2 ? params                              // passed params replace url params
          : merge_mode === 1  ? $.extend( {}, params, url_params )  // url params override passed params
          : $.extend( {}, url_params, params );                     // passed params override url params
        
        // Convert params object to a string.
        qs = jq_param( qs );
      }
      
      // Build URL from the base url, querystring and hash. In 'querystring'
      // mode, ? is only added if a query string exists. In 'fragment' mode, #
      // is always added.
      result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash;
      
    } else {
      // If URL was passed in, parse params from URL string, otherwise parse
      // params from window.location.
      result = get_func( url !== undefined ? url : loc[ str_href ] );
    }
    
    return result;
  };
  
  jq_param[ str_querystring ]                  = curry( jq_param_sub, 0, get_querystring );
  jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment );
  
  // Section: Deparam (from string)
  // 
  // Method: jQuery.deparam
  // 
  // Deserialize a params string into an object, optionally coercing numbers,
  // booleans, null and undefined values; this method is the counterpart to the
  // internal jQuery.param method.
  // 
  // Usage:
  // 
  // > jQuery.deparam( params [, coerce ] );
  // 
  // Arguments:
  // 
  //  params - (String) A params string to be parsed.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  $.deparam = jq_deparam = function( params, coerce ) {
    var obj = {},
      coerce_types = { 'true': !0, 'false': !1, 'null': null };
    
    // Iterate over all name=value pairs.
    $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){
      var param = v.split( '=' ),
        key = decode( param[0] ),
        val,
        cur = obj,
        i = 0,
        
        // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
        // into its component parts.
        keys = key.split( '][' ),
        keys_last = keys.length - 1;
      
      // If the first keys part contains [ and the last ends with ], then []
      // are correctly balanced.
      if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
        // Remove the trailing ] from the last keys part.
        keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
        
        // Split first keys part into two parts on the [ and add them back onto
        // the beginning of the keys array.
        keys = keys.shift().split('[').concat( keys );
        
        keys_last = keys.length - 1;
      } else {
        // Basic 'foo' style key.
        keys_last = 0;
      }
      
      // Are we dealing with a name=value pair, or just a name?
      if ( param.length === 2 ) {
        val = decode( param[1] );
        
        // Coerce values.
        if ( coerce ) {
          val = val && !isNaN(val)            ? +val              // number
            : val === 'undefined'             ? undefined         // undefined
            : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
            : val;                                                // string
        }
        
        if ( keys_last ) {
          // Complex key, build deep object structure based on a few rules:
          // * The 'cur' pointer starts at the object top-level.
          // * [] = array push (n is set to array length), [n] = array if n is 
          //   numeric, otherwise object.
          // * If at the last keys part, set the value.
          // * For each keys part, if the current level is undefined create an
          //   object or array based on the type of the next keys part.
          // * Move the 'cur' pointer to the next level.
          // * Rinse & repeat.
          for ( ; i <= keys_last; i++ ) {
            key = keys[i] === '' ? cur.length : keys[i];
            cur = cur[key] = i < keys_last
              ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
              : val;
          }
          
        } else {
          // Simple key, even simpler rules, since only scalars and shallow
          // arrays are allowed.
          
          if ( $.isArray( obj[key] ) ) {
            // val is already an array, so push on the next value.
            obj[key].push( val );
            
          } else if ( obj[key] !== undefined ) {
            // val isn't an array, but since a second value has been specified,
            // convert val into an array.
            obj[key] = [ obj[key], val ];
            
          } else {
            // val is a scalar.
            obj[key] = val;
          }
        }
        
      } else if ( key ) {
        // No value was defined, so set something meaningful.
        obj[key] = coerce
          ? undefined
          : '';
      }
    });
    
    return obj;
  };
  
  // Method: jQuery.deparam.querystring
  // 
  // Parse the query string from a URL or the current window.location,
  // deserializing it into an object, optionally coercing numbers, booleans,
  // null and undefined values.
  // 
  // Usage:
  // 
  // > jQuery.deparam.querystring( [ url ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  url - (String) An optional params string or URL containing query string
  //    params to be parsed. If url is omitted, the current window.location
  //    is used.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  // Method: jQuery.deparam.fragment
  // 
  // Parse the fragment (hash) from a URL or the current window.location,
  // deserializing it into an object, optionally coercing numbers, booleans,
  // null and undefined values.
  // 
  // Usage:
  // 
  // > jQuery.deparam.fragment( [ url ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  url - (String) An optional params string or URL containing fragment (hash)
  //    params to be parsed. If url is omitted, the current window.location
  //    is used.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  function jq_deparam_sub( is_fragment, url_or_params, coerce ) {
    if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) {
      // url_or_params not specified.
      coerce = url_or_params;
      url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ]();
    } else {
      url_or_params = is_string( url_or_params )
        ? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' )
        : url_or_params;
    }
    
    return jq_deparam( url_or_params, coerce );
  };
  
  jq_deparam[ str_querystring ]                    = curry( jq_deparam_sub, 0 );
  jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, 1 );
  
  // Section: Element manipulation
  // 
  // Method: jQuery.elemUrlAttr
  // 
  // Get the internal "Default URL attribute per tag" list, or augment the list
  // with additional tag-attribute pairs, in case the defaults are insufficient.
  // 
  // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list
  // is used to determine which attribute contains the URL to be modified, if
  // an "attr" param is not specified.
  // 
  // Default Tag-Attribute List:
  // 
  //  a      - href
  //  base   - href
  //  iframe - src
  //  img    - src
  //  input  - src
  //  form   - action
  //  link   - href
  //  script - src
  // 
  // Usage:
  // 
  // > jQuery.elemUrlAttr( [ tag_attr ] );
  // 
  // Arguments:
  // 
  //  tag_attr - (Object) An object containing a list of tag names and their
  //    associated default attribute names in the format { tag: 'attr', ... } to
  //    be merged into the internal tag-attribute list.
  // 
  // Returns:
  // 
  //  (Object) An object containing all stored tag-attribute values.
  
  // Only define function and set defaults if function doesn't already exist, as
  // the urlInternal plugin will provide this method as well.
  $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) {
    return $.extend( elemUrlAttr_cache, obj );
  })({
    a: str_href,
    base: str_href,
    iframe: str_src,
    img: str_src,
    input: str_src,
    form: 'action',
    link: str_href,
    script: str_src
  });
  
  jq_elemUrlAttr = $[ str_elemUrlAttr ];
  
  // Method: jQuery.fn.querystring
  // 
  // Update URL attribute in one or more elements, merging the current URL (with
  // or without pre-existing query string params) plus any params object or
  // string into a new URL, which is then set into that attribute. Like
  // <jQuery.param.querystring (build url)>, but for all elements in a jQuery
  // collection.
  // 
  // Usage:
  // 
  // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  attr - (String) Optional name of an attribute that will contain a URL to
  //    merge params or url into. See <jQuery.elemUrlAttr> for a list of default
  //    attributes.
  //  params - (Object) A params object to be merged into the URL attribute.
  //  params - (String) A URL containing query string params, or params string
  //    to be merged into the URL attribute.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  //    
  //    * 0: params in the params argument will override any params in attr URL.
  //    * 1: any params in attr URL will override params in the params argument.
  //    * 2: params argument will completely replace any query string in attr
  //         URL.
  // 
  // Returns:
  // 
  //  (jQuery) The initial jQuery collection of elements, but with modified URL
  //  attribute values.
  
  // Method: jQuery.fn.fragment
  // 
  // Update URL attribute in one or more elements, merging the current URL (with
  // or without pre-existing fragment/hash params) plus any params object or
  // string into a new URL, which is then set into that attribute. Like
  // <jQuery.param.fragment (build url)>, but for all elements in a jQuery
  // collection.
  // 
  // Usage:
  // 
  // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  attr - (String) Optional name of an attribute that will contain a URL to
  //    merge params into. See <jQuery.elemUrlAttr> for a list of default
  //    attributes.
  //  params - (Object) A params object to be merged into the URL attribute.
  //  params - (String) A URL containing fragment (hash) params, or params
  //    string to be merged into the URL attribute.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  //    
  //    * 0: params in the params argument will override any params in attr URL.
  //    * 1: any params in attr URL will override params in the params argument.
  //    * 2: params argument will completely replace any fragment (hash) in attr
  //         URL.
  // 
  // Returns:
  // 
  //  (jQuery) The initial jQuery collection of elements, but with modified URL
  //  attribute values.
  
  function jq_fn_sub( mode, force_attr, params, merge_mode ) {
    if ( !is_string( params ) && typeof params !== 'object' ) {
      // force_attr not specified.
      merge_mode = params;
      params = force_attr;
      force_attr = undefined;
    }
    
    return this.each(function(){
      var that = $(this),
        
        // Get attribute specified, or default specified via $.elemUrlAttr.
        attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '',
        
        // Get URL value.
        url = attr && that.attr( attr ) || '';
      
      // Update attribute with new URL.
      that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) );
    });
    
  };
  
  $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring );
  $.fn[ str_fragment ]    = curry( jq_fn_sub, str_fragment );
  
  // Section: History, hashchange event
  // 
  // Method: jQuery.bbq.pushState
  // 
  // Adds a 'state' into the browser history at the current position, setting
  // location.hash and triggering any bound <window.onhashchange> event
  // callbacks (provided the new state is different than the previous state).
  // 
  // If no arguments are passed, an empty state is created, which is just a
  // shortcut for jQuery.bbq.pushState( {}, 2 ).
  // 
  // Usage:
  // 
  // > jQuery.bbq.pushState( [ params [, merge_mode ] ] );
  // 
  // Arguments:
  // 
  //  params - (String) A serialized params string or a hash string beginning
  //    with # to merge into location.hash.
  //  params - (Object) A params object to merge into location.hash.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified (unless a hash string beginning with # is specified, in which
  //    case merge behavior defaults to 2), and is as-follows:
  // 
  //    * 0: params in the params argument will override any params in the
  //         current state.
  //    * 1: any params in the current state will override params in the params
  //         argument.
  //    * 2: params argument will completely replace current state.
  // 
  // Returns:
  // 
  //  Nothing.
  // 
  // Additional Notes:
  // 
  //  * Setting an empty state may cause the browser to scroll.
  //  * Unlike the fragment and querystring methods, if a hash string beginning
  //    with # is specified as the params agrument, merge_mode defaults to 2.
  
  jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) {
    if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) {
      // Params string begins with # and merge_mode not specified, so completely
      // overwrite window.location.hash.
      merge_mode = 2;
    }
    
    var has_args = params !== undefined,
      // Merge params into window.location using $.param.fragment.
      url = jq_param_fragment( loc[ str_href ], has_args ? params : {}, has_args ? merge_mode : 2 );
    
    // Set new window.location.href. If hash is empty, use just # to prevent
    // browser from reloading the page. Note that Safari 3 & Chrome barf on
    // location.hash = '#'.
    loc[ str_href ] = url + ( /#/.test( url ) ? '' : '#' );
  };
  
  
  // Method: jQuery.bbq.getState
  // 
  // Retrieves the current 'state' from the browser history, parsing
  // location.hash for a specific key or returning an object containing the
  // entire state, optionally coercing numbers, booleans, null and undefined
  // values.
  // 
  // Usage:
  // 
  // > jQuery.bbq.getState( [ key ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  key - (String) An optional state key for which to return a value.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false.
  // 
  // Returns:
  // 
  //  (Anything) If key is passed, returns the value corresponding with that key
  //    in the location.hash 'state', or undefined. If not, an object
  //    representing the entire 'state' is returned.
  
  jq_bbq.getState = jq_bbq_getState = function( key, coerce ) {
    return key === undefined || typeof key === 'boolean'
      ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here
      : jq_deparam_fragment( coerce )[ key ];
  };
  
  // Method: jQuery.bbq.removeState
  // 
  // Remove one or more keys from the current browser history 'state', creating
  // a new state, setting location.hash and triggering any bound
  // <window.onhashchange> event callbacks (provided the new state is different
  // than the previous state).
  // 
  // If no arguments are passed, an empty state is created, which is just a
  // shortcut for jQuery.bbq.pushState( {}, 2 ).
  // 
  // Usage:
  // 
  // > jQuery.bbq.removeState( [ key [, key ... ] ] );
  // 
  // Arguments:
  // 
  //  key - (String) One or more key values to remove from the current state,
  //    passed as individual arguments.
  //  key - (Array) A single array argument that contains a list of key values
  //    to remove from the current state.
  // 
  // Returns:
  // 
  //  Nothing.
  // 
  // Additional Notes:
  // 
  //  * Setting an empty state may cause the browser to scroll.
  
  jq_bbq.removeState = function( arr ) {
    var state = {};
    
    // If one or more arguments is passed..
    if ( arr !== undefined ) {
      
      // Get the current state.
      state = jq_bbq_getState();
      
      // For each passed key, delete the corresponding property from the current
      // state.
      $.each( $.isArray( arr ) ? arr : arguments, function(i,v){
        delete state[ v ];
      });
    }
    
    // Set the state, completely overriding any existing state.
    jq_bbq_pushState( state, 2 );
  };
  
  // Event: window.onhashchange
  // 
  // Usage in 1.4a2 and newer:
  // 
  // In 1.4a2 and newer, the event object that is passed into the callback is
  // augmented with an additional e.fragment property that contains the current
  // document location.hash state as a string, as well as an e.getState method.
  // 
  // e.fragment is equivalent to the output of <jQuery.param.fragment>, and
  // e.getState() is equivalent to <jQuery.bbq.getState>, except that they refer
  // to the event-specific state value stored in the event object, instead of
  // the current window.location, allowing the event object to be referenced
  // later, even if window.location has changed.
  // 
  // > $(window).bind( 'hashchange', function(e) {
  // >   var hash_str = e.fragment,
  // >     param_obj = e.getState(),
  // >     param_val = e.getState( 'param_name' ),
  // >     param_val_coerced = e.getState( 'param_name', true );
  // >   ...
  // > });
  // 
  // Usage in 1.3.2:
  // 
  // In 1.3.2, the event object is unable to be augmented as in 1.4a2+, so the
  // fragment state isn't bound to the event object and must instead be parsed
  // using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods.
  // 
  // > $(window).bind( 'hashchange', function(e) {
  // >   var hash_str = $.param.fragment(),
  // >     param_obj = $.bbq.getState(),
  // >     param_val = $.bbq.getState( 'param_name' ),
  // >     param_val_coerced = $.bbq.getState( 'param_name', true );
  // >   ...
  // > });
  // 
  // Additional Notes:
  // 
  // * See <jQuery hashchange event> for more detailed information.
  
  jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
    
    // Augmenting the event object with the .fragment property and .getState
    // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
    // work, but the event won't be augmented)
    add: function( handler, data, namespaces ) {
      return function(e) {
        // e.fragment is set to the value of location.hash (with any leading #
        // removed) at the time the event is triggered.
        var hash = e[ str_fragment ] = jq_param_fragment();
        
        // e.getState() works just like $.bbq.getState(), but uses the
        // e.fragment property stored on the event object.
        e.getState = function( key, coerce ) {
          return key === undefined || typeof key === 'boolean'
            ? jq_deparam( hash, key ) // 'key' really means 'coerce' here
            : jq_deparam( hash, coerce )[ key ];
        };
        
        handler.apply( this, arguments );
      };
    }
    
  });
  
})(jQuery,this);

/*!
 * jQuery hashchange event - v1.0 - 1/9/2010
 * http://benalman.com/projects/jquery-hashchange-plugin/
 * 
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Script: jQuery hashchange event
//
// *Version: 1.0, Last updated: 1/9/2010*
// 
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub       - http://github.com/cowboy/jquery-hashchange/
// Source       - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified)   - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// This working example, complete with fully commented code, illustrate one way
// in which this plugin can be used.
// 
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4a2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.
// Unit Tests      - http://benalman.com/code/projects/jquery-hashchange/unit/
// 
// About: Known issues
// 
// While this jQuery hashchange event implementation is quite stable and robust,
// there are a few unfortunate browser bugs surrounding expected hashchange
// event-based behaviors, independent of any JavaScript window.onhashchange
// abstraction. See the following examples for more information:
// 
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// 
// About: Release History
// 
// 1.0   - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
//         window.onhashchange functionality into a separate plugin for users
//         who want just the basic event & back button support, without all the
//         extra awesomeness that BBQ provides. This plugin will be included as
//         part of jQuery BBQ, but also be available separately.

(function($,window){
  '$:nomunge'; // Used by YUI compressor.
  
  // A convenient shortcut.
  var loc = window.location,
    
    // Method / object references.
    fake_onhashchange,
    jq_event_special = $.event.special,
    
    // Reused strings.
    str_hashchange = 'hashchange',
    
    // IE6/7 specifically need some special love when it comes to back-button
    // support, so let's do a little browser sniffing..
    browser = $.browser,
    is_old_ie = browser.msie && browser.version < 8,
    
    // Does the browser support window.onhashchange? Test for IE version, since
    // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
    supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
  
  // Get location.hash (or what you'd expect location.hash to be) sans any
  // leading #. Thanks for making this necessary, Firefox!
  function get_fragment( url ) {
    url = url || loc.href;
    return url.replace( /^[^#]*#?(.*)$/, '$1' );
  };
  
  // Property: jQuery.hashchangeDelay
  // 
  // The numeric interval (in milliseconds) at which the <window.onhashchange>
  // polling loop executes. Defaults to 100.
  
  $[ str_hashchange + 'Delay' ] = 100;
  
  // Event: window.onhashchange
  // 
  // Fired when window.location.hash changes. In browsers that support it, the
  // native window.onhashchange event is used (IE8, FF3.6), otherwise a polling
  // loop is initialized, running every <jQuery.hashchangeDelay> milliseconds
  // to see if the hash has changed. In IE 6 and 7, a hidden IFRAME is created
  // to allow the back button and hash-based history to work.
  // 
  // Usage:
  // 
  // > $(window).bind( 'hashchange', function(e) {
  // >   var hash = location.hash;
  // >   ...
  // > });
  // 
  // Additional Notes:
  // 
  // * The polling loop and iframe are not created until at least one callback
  //   is actually bound to 'hashchange'.
  // * If you need the bound callback(s) to execute immediately, in cases where
  //   the page 'state' exists on page load (via bookmark or page refresh, for
  //   example) use $(window).trigger( 'hashchange' );
  
  jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
    
    // Called only when the first 'hashchange' event is bound to window.
    setup: function() {
      // If window.onhashchange is supported natively, there's nothing to do..
      if ( supports_onhashchange ) { return false; }
      
      // Otherwise, we need to create our own. And we don't want to call this
      // until the user binds to the event, just in case they never do, since it
      // will create a polling loop and possibly even a hidden IFRAME.
      fake_onhashchange.start();
    },
    
    // Called only when the last 'hashchange' event is unbound from window.
    teardown: function() {
      // If window.onhashchange is supported natively, there's nothing to do..
      if ( supports_onhashchange ) { return false; }
      
      // Otherwise, we need to stop ours (if possible).
      fake_onhashchange.stop();
    }
    
  });
  
  // fake_onhashchange does all the work of triggering the window.onhashchange
  // event for browsers that don't natively support it, including creating a
  // polling loop to watch for hash changes and in IE 6/7 creating a hidden
  // IFRAME to enable back and forward.
  fake_onhashchange = (function(){
    var self = {},
      timeout_id,
      iframe,
      set_history,
      get_history;
    
    // Initialize. In IE 6/7, creates a hidden IFRAME for history handling.
    function init(){
      // Most browsers don't need special methods here..
      set_history = get_history = function(val){ return val; };
      
      // But IE6/7 do!
      if ( is_old_ie ) {
        
        // Create hidden IFRAME at the end of the body.
        iframe = $('<iframe src="javascript:0"/>').hide().appendTo( 'body' )[0].contentWindow;
        
        // Get history by looking at the hidden IFRAME's location.hash.
        get_history = function() {
          return get_fragment( iframe.document.location.href );
        };
        
        // Set a new history item by opening and then closing the IFRAME
        // document, *then* setting its location.hash.
        set_history = function( hash, history_hash ) {
          if ( hash !== history_hash ) {
            var doc = iframe.document;
            doc.open().close();
            doc.location.hash = '#' + hash;
          }
        };
        
        // Set initial history.
        set_history( get_fragment() );
      }
    };
    
    // Start the polling loop.
    self.start = function() {
      // Polling loop is already running!
      if ( timeout_id ) { return; }
      
      // Remember the initial hash so it doesn't get triggered immediately.
      var last_hash = get_fragment();
      
      // Initialize if not yet initialized.
      set_history || init();
      
      // This polling loop checks every $.hashchangeDelay milliseconds to see if
      // location.hash has changed, and triggers the 'hashchange' event on
      // window when necessary.
      (function loopy(){
        var hash = get_fragment(),
          history_hash = get_history( last_hash );
        
        if ( hash !== last_hash ) {
          set_history( last_hash = hash, history_hash );
          
          $(window).trigger( str_hashchange );
          
        } else if ( history_hash !== last_hash ) {
          loc.href = loc.href.replace( /#.*/, '' ) + '#' + history_hash;
        }
        
        timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] );
      })();
    };
    
    // Stop the polling loop, but only if an IE6/7 IFRAME wasn't created. In
    // that case, even if there are no longer any bound event handlers, the
    // polling loop is still necessary for back/next to work at all!
    self.stop = function() {
      if ( !iframe ) {
        timeout_id && clearTimeout( timeout_id );
        timeout_id = 0;
      }
    };
    
    return self;
  })();
  
})(jQuery,this);
(function($){
    
    var pagingStructure = null;
    /**
     * Pages a list of items.
     * @param loadCallback <code>jqXHR function(targetElement, selectedPage, options)</code>
     *                     A function which gets called immediately.  This function must handle
     *                     the load and display of the newly selected page.
     * @param callbackOptions An object containing options to be passed to the callback function.
     */
    var PercResultsPaging = function(loadCallback, callbackOptions)
    {
        var elem = this.get(0);
        if(pagingStructure === null)
        {
            buildPagingStructure();
        }
        elem.totalPages = 1;
        elem.totalEntries = 0;
        
        // Pulls the 'data' attr from the current element, and does a json parse on it.
        // Then, grabs the maxResults value, and parseInt on it, making sure to use base 10.        
        if(!(parseInt(elem.settings.maxResults, 10) > 0)) // Might already be defined by the element itself, or might be defined, but NaN.
        {
            elem.settings.maxResults = parseInt($.parseJSON(this.attr('data')).maxResults, 10);
            if (isNaN(elem.settings.maxResults) || elem.settings.maxResults < 1)
                elem.settings.maxResults = 0;
        }
        quickNav(elem, 1, loadCallback, callbackOptions);
        return this;
    };
    
    var updateNav = function(target, navLoc, callback, callbackOptions)
    {
        currDiv = $(target);
        var currPaging = currDiv.find('.perc-pagination-container');
        if(currPaging.length > 0)
            currPaging.replaceWith(pagingStructure.clone());
        else
            currDiv.append(pagingStructure.clone());
        
        // If we've decided that it's only one page long, don't bother paging.
        if(target.totalPages <= 1 || !(parseInt(target.totalPages, 10) === target.totalPages))
        {
            currDiv.find('.perc-pagination-container').hide();
            return;
        }
        else
        {
            currDiv.find('.perc-pagination-container').show();
        }
        
        navLoc = +navLoc; // String to Number Coercion
        
        currDiv.find('.perc-current-page').removeClass('perc-current-page');
        
        // Clean invalid values.
        if (navLoc < 1)
            navLoc = 1;
        else if (navLoc > target.totalPages)
            navLoc = target.totalPages;
        // Sane defaults
        var center = 3;
        var navLocClass = '.perc-third';
        
        // Do we need multi-paging?  (Scrolls with you as you advance through pages)
        if (target.totalPages >= 5)
        {
            if (navLoc < 3)  // Special case to clamp beginning of paging to the left side.
            {
                center = 3;
                if (navLoc === 1)
                    navLocClass = '.perc-first';  // Which element gets perc-current class for bolding
                else if (navLoc === 2)
                    navLocClass = '.perc-second';
            }
            else if (navLoc > (target.totalPages - 2)) // Special case to clamp end of paging to the right side.
            {
                center = target.totalPages - 2;
                if (navLoc === target.totalPages)
                    navLocClass = '.perc-fifth';
                else if (navLoc === (target.totalPages - 1))
                    navLocClass = '.perc-fourth';
            }
            else // Somewhere in the middle, everything is standard.
            {
                center = navLoc;
                navLocClass = '.perc-third';
            }
        }
        else
        {
            // Simple paging is simple.
            center = 3;
            
            if(navLoc === 1)
                navLocClass = '.perc-first';
            else if(navLoc === 2)
                navLocClass = '.perc-second';
            else if(navLoc === 3)
                navLocClass = '.perc-third';
            else
                navLocClass = '.perc-fourth';
        }
        
        // Hide all numbers first, in case we don't need to use them.
        currDiv.find('.perc-goto').hide().removeClass('perc-comma-after');
        
        /*
           A lot of this is fairly boilerplate, however, since we've also got a lot of distinct
           values in each instance, I've written it out fully.  We could package these into smaller
           methods (indeed, they used to be like that), but we would end up passing in most of the
           function into the arguments, so I've left it separate for now.  In general, the ordering
           is 1) find the right div 2) show it 3) set it's text to the right number 4) unbind
           previous click events 5) bind a new click event.  The click even is just a call to
           quickNav with the second argument varying.  Second argument should always be equal to the
           .text()
        */
        
        // First and prev nav.  Also 1 and 2, since we've got at least two pages.
        if(target.totalPages >= 2)
        {
            // Make sure to unbind click first, goes crazy otherwise.
            currDiv.find('.perc-goto-first').unbind('click').click(function() {
                quickNav(target, 1, callback, callbackOptions);
            });
            currDiv.find('.perc-goto-pre').unbind('click').click(function() {
                quickNav(target, navLoc - 1, callback, callbackOptions);
            });
            
            // perc-comma-after is a cheap hack to allow a stylesheet to modify what gets show
            // .perc-comma-after:after{ content: ","; }
            currDiv.find('.perc-first').show().text(center - 2).unbind('click').click(function() {
                    quickNav(target, center - 2, callback, callbackOptions);
                }).addClass('perc-comma-after');
            // Unbind so we don't conflict with earlier calls of the same thing.
            currDiv.find('.perc-second').show().text(center - 1).unbind('click').click(function(){
                    quickNav(target, center - 1, callback, callbackOptions);
                });
        }
        if(target.totalPages >= 3)
        {
            // If we have 3 or more pages, we need to add a comma to the second page.
            currDiv.find('.perc-second').addClass('perc-comma-after');
            currDiv.find('.perc-third').show().text(center + 0).unbind('click').click(function(){
                    // We're passing in the same parameters to quickNav that we're getting from it,
                    // so it becomes a user initiated recursive function.
                    quickNav(target, center + 0, callback, callbackOptions);
                });
        }
        if(target.totalPages >= 4)
        {
            currDiv.find('.perc-third').addClass('perc-comma-after');
            currDiv.find('.perc-fourth').show().text(center + 1).unbind('click').click(function(){
                    quickNav(target, center + 1, callback, callbackOptions);
                });
        }
        if(target.totalPages >= 5)
        {
            currDiv.find('.perc-fourth').addClass('perc-comma-after');
            currDiv.find('.perc-fifth').show().text(center + 2).unbind('click').click(function(){
                    quickNav(target, center + 2, callback, callbackOptions);
                });
        }
        // Next and last nav.
        if(target.totalPages >= 2)
        {
            currDiv.find('.perc-goto-next').unbind('click').click(function() {
                quickNav(target, navLoc + 1, callback, callbackOptions);
            });
            currDiv.find('.perc-goto-last').unbind('click').click(function() {
                quickNav(target, target.totalPages, callback, callbackOptions);
            });
        }
        
        currDiv.find(navLocClass).addClass('perc-current-page');
        // Update page count.
        currDiv.find('.perc-page-count .perc-page-count-current').text(navLoc + " " + target.settings.pagingOfText);
        currDiv.find('.perc-page-count .perc-page-count-total').text(target.totalPages + " " + target.settings.pagingPagesText);
    };
    
    /**
     * This gets called every time a new page is clicked.  This function is a delegator
     * for the other functions which handle work.
     * @name quickNav
     * @param target Element to have paging applied to.
     * @param navLoc Page number to jump to. 
     * @param callback Callback function to call before updating navigation.  
     *                 Usually a refresh function for grabbing the results on the new page.
     *                 Signature is <code>callback(target, navLoc, callbackOptions)</code>
     * @param callbackOptions Options to pass to the callback function.
     *                        Usually an object containing name-value pairs.
     */
    quickNav = function(target, navLoc, callback, callbackOptions) {
        if(navLoc > target.totalPages)
        {
            navLoc = target.totalPages;
        }
        else if(navLoc < 1)
        {
            navLoc = 1;
        }
        var jqXHR = callback(target, navLoc, callbackOptions);
        
        jqXHR.success(function(result, status)
        {
            if(parseInt(result.totalEntries, 10) > 0)
            {
                target.totalEntries = parseInt(result.totalEntries, 10);
            }
            target.totalPages = Math.ceil((target.totalEntries / target.settings.maxResults));
            updateNav(target, navLoc, callback, callbackOptions);
        });
    };
    /**
     * Builds the necessary html structure to hold the paging.
     * Is called once upon load of first page, and not after.
     */
    function buildPagingStructure()
    {
        var structure = $('<div />').
            addClass('perc-pagination-container').
            append(
                $('<span />').
                    addClass('perc-page-count').
                    append(
                        $('<span />').
                            addClass('perc-page-count-current')
                    ).
                    append(' ').
                    append(
                        $('<span />').
                            addClass('perc-page-count-total')
                    )
            ).append(
                $('<span />').
                    addClass('perc-page-nav').
                    append(
                        $('<span />').
                            addClass('perc-goto-quick-nav').
                            addClass('perc-goto-first')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto-quick-nav').
                            addClass('perc-goto-pre')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto').
                            addClass('perc-first')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto').
                            addClass('perc-second')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto').
                            addClass('perc-third')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto').
                            addClass('perc-fourth')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto').
                            addClass('perc-fifth')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto-quick-nav').
                            addClass('perc-goto-next')
                    ).
                    append(
                        $('<span />').
                            addClass('perc-goto-quick-nav').
                            addClass('perc-goto-last')
                    )
            );
        pagingStructure = structure;
    }
    // Assign to jQuery.
    $.fn.PercResultsPaging = PercResultsPaging;
})(jQuery);/**
 * Page list service, makes a call to the server and gets the page list entries.
 */
(function($)
{
    $.PercArchiveListService = {
        getArchiveEntries : getArchiveEntries
    };
    function getArchiveEntries(queryString, callback)
    {
        /* Mock data
        var resultData = {years : [
            {name : "2011",
             months:[
                   {name : "February", counts : 10},
                   {name : "January", counts : 20}
             ]},
             {name : "2010",
              months:[
                   {name : "February", counts : 5},
                   {name : "January", counts : 15}
              ]}
        ]};
        callback(resultData);
        */
        var serviceUrl = "/perc-metadata-services/metadata/blogs/get";
        $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString); 
    }
})(jQuery);/**
 * Blog list service, makes a call to the server and gets the blog list entries.
 */
(function($)
{
    $.PercBlogListService = {
        getPageEntries : getPageEntries
    };
    function getPageEntries(queryString, callback)
    {
        var serviceUrl = "http://www.cuidadodesalud.gov/enes/perc-metadata-services/metadata/get";
        return $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
    }
})(jQuery);/**
 * Blog list service, makes a call to the server and gets the blog list entries.
 */
(function($)
{
    $.PercBlogPostService = {
        getPostNavEntries : getPostNavEntries
    };
    function getPostNavEntries(queryString, pagePath, callback)
    {
        var pagePathId = "?currentPageId=" + pagePath;
        var serviceUrl = "/perc-metadata-services/metadata/blog/getCurrent" + pagePathId;
              
        $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
        
        /*var results = [
            {"site":"SiteTest","pagepath":"/SiteTestapps/ROOT/BlogPost1","folder":"/","linktext":"BlogPost1","name":"BlogPost1","type":"page"},
            {"site":"SiteTest","pagepath":"/SiteTestapps/ROOT/BlogPost2","folder":"/","linktext":"BlogPost2","name":"BlogPost2","type":"page"},
            {"site":"SiteTest","pagepath":"/SiteTestapps/ROOT/BlogPost3","folder":"/","linktext":"BlogPost3","name":"PageTest2","type":"page"}
        ];
        callback(true,results);*/
    }
})(jQuery);/**
 * Category list service, makes a call to the server and gets the category list entries.
 */
(function($)
{
    $.PercCategoryListService = {
        getCategories : getCategories
    };
    function getCategories(queryString, callback)
    {
        /*
	    var mock = [
            {category : "QQ"},
        	{"category":"B","children":[
            		{"category":"C","children":null,"count":{"second":2,"first":2}},
            		{"category":"D","children":[
            			{"category":"E","children":[{category:"E1",children:[{category:"E2", children:[{category:"E3", children:null}]}]}],"count":{"second":1,"first":1}}],"count":{"second":4,"first":3}},
            		{"category":"F","children":null,"count":{"second":4,"first":4}}],"count":{"second":10,"first":1}}];
    	*/
        var serviceUrl = "/perc-metadata-services/metadata/categories/get";
        $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
    }
})(jQuery);

/**
 * PercLikedService.js
 * @author Wesley Hirsch
 * @author Jose Annunziato
 * Service which handles LikedWidget function calls.
 * 
 * (*) Dependencies
 * /jslib/jquery.cookie.js
 * 
 */
(function($){
    var likesServiceUrl = "/perc-comments-services/likes/";
	
	var constants = {
        // Set Constants
        "PERC_TYPE_PAGE"                          : "PAGE",
        "PERC_TYPE_COMMENT"                       : "COMMENT",
        "PERC_TYPE_IMAGE"                         : "IMAGE",
		
		"PERC_ONE_YEAR"                            : 365
	};
	
    /**
     * Increments the number of times this item has been liked.
     * @param callback (function) function to call with response from server
     */
    var likeThis = function(callback) {
		if(typeof(callback) === "undefined" || callback === null) return false;
		
		likePathname = window.location.pathname;
		/*if(likePathname[0] == "/")
		{
			likePathname = likePathname.substring(1);
		}*/
		
		like(this.siteName, likePathname, constants.PERC_TYPE_PAGE, callback);
    };
    /**
     * Increments the number of times the given item has been liked.
     * @param site (string) domain/host where this item is hosted
     * @param id (string) unique identifier of item within the host
     * @param type (string) type of the item, for now it's only "PAGE", "COMMENTS" and "IMAGE"
     * @param callback (function) function to call with response from server
     */
    var like = function(site, id, type, callback) {
        var likeServiceUrl = likesServiceUrl + "like/" + site + "/" + type + "/" + id;
        $.PercServiceUtils.makeRequest(likeServiceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                var data = {url : site, totalLikes : results.data};
				setThisLiked(true, data.url);
                callback(true, data);
            }
            else{
                callback(false, null);
            }
        });
    };
    /**
     * Decrements the number of times this item has been liked.
     * @param callback (function) function to call with response from server
     */
    var unlikeThis = function(callback) {
		if(typeof(callback) === "undefined" || callback === null) return false;
	
		likePathname = window.location.pathname;
		/*if(likePathname[0] == "/")
		{
			likePathname = likePathname.substring(1);
		}*/
		
		unlike(this.siteName, likePathname, constants.PERC_TYPE_PAGE, callback);
    };
	/**
     * Decrements the number of times this item has been liked.
     * @param site (string) domain/host where this item is hosted
     * @param id (string) unique identifier of item within the host
     * @param type (string) type of the item, for now it's only "PAGE", "COMMENTS" and "IMAGE"
     * @param callback (function) function to call with response from server
     */
	var unlike = function(site, id, type, callback) {
        var likeServiceUrl = likesServiceUrl + "unlike/" + site + "/" + type + "/" + id;
        $.PercServiceUtils.makeRequest(likeServiceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                var data = {url : site, totalLikes : results.data};
				setThisLiked(false, data.url);
                callback(true, data);
            }
            else{
                callback(false, null);
            }
        });
    };
    /**
     * Gets the total number of times this page has been liked.
     * @param callback (function) function to call with response from server
     */
    var getTotalLikesForThisPage = function(callback) {
		likePathname = window.location.pathname;
		/*if(likePathname[0] == "/")
		{
			likePathname = likePathname.substring(1);
		}*/
		getTotalLikes(this.siteName, likePathname, constants.PERC_TYPE_PAGE, callback);
    };
        
	/**
     * Gets the total number of times the given path has been liked.
     * @param site (string) domain/host where this item is hosted
     * @param id (string) unique identifier of item within the host
     * @param type (string) type of the item, for now it's only "PAGE", "COMMENTS" and "IMAGE"
     * @param callback (function) function to call with response from server
     */
    var getTotalLikes = function(site, id, type, callback) {
        var likeServiceUrl = likesServiceUrl + "total/" + site + "/" + type + "/" + id;
        $.PercServiceUtils.makeRequest(likeServiceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                var data = {url : site, totalLikes : results.data};
                callback(true, data);
            }
            else{
                callback(false, null);
            }
        });
    };
	/**
	 * Returns true or false for whether or not the current page is liked.
	 * @return isLiked boolean true if liked, boolean false if not liked.
	 */
	var isThisLiked = function() {
		
		var pathname = window.location.pathname;
		return isLiked(window.location.protocol + "//" + this.siteName + likeId(pathname));
	};
	/**
	 * Returns true or false for whether or not the selected page is liked.
	 * @param uri fully qualified URI (not url) of page to query for.
	 * @return isLiked boolean true if liked, boolean false if not liked.
	 */
	var isLiked = function(url) {
		if(typeof(url) === "undefined" || url === null) return false;
		
		var likedState = $.cookie(url);
		if (likedState === null)
			return false;
		// Not strictly necessary, but I belive it makes for more readable code.
		if(likedState.toLowerCase() === 'true')
			return true;
		else
			return false;
		
	};
	/**
	 * Resolves /index.*, /home.*, /default.* or  / to always be /  and if anything else just return the provided pathname
	 * @param pathname
	 * @return resolved pathname
	 */
	var likeId = function(pathname){
		
		var id = "";
		if(pathname.lastIndexOf('.') != -1)
        {
            id = pathname.substring(0, pathname.lastIndexOf('.'));
        }
		if((pathname === "/") || (pathname ==="/index") ||(id === "/index") || (pathname ==="/home") || (pathname ==="/default"))
        {
            id = "/"; 
        }
        else
        {
            id = pathname;
        }
        return id;
	};
	/**
	 * Sets whether or not the current page is liked.
	 * @param liked boolean true if liked, boolean false if not liked.
	 * @return success boolean true if setting of liked succeded, boolean false if it failed.
	 */
	var setThisLiked = function(liked,siteName) {
		if(typeof(liked) === "undefined" || liked === null) return false;
		return setLiked(window.location.protocol + "//" + siteName + window.location.pathname, liked);
	};
	/**
	 * Sets whether or not the selected page is liked.
	 * @param uri fully qualified URI (not url) of page to query for.  URI MUST NOT CONTAIN QUERY STRING OR HASH VALUE.
	 * @param liked boolean true if liked, boolean false if not liked.
	 * @return success boolean true if setting of liked succeded, boolean false if it failed.
	 */
	var setLiked = function(url, liked) {
		if(typeof(url) === "undefined" || url === null) return false;
		if(typeof(liked) === "undefined" || liked === null) return false;
		var likedString = null;
		if(liked === true)
			likedString = "true";
		else if(liked === false)
			likedString = null;
		
		$.cookie(url,likedString, {"expires" : constants.PERC_ONE_YEAR});
		
	};
    var PercLikedService = {
        "constants" : constants,
        "like" : like,
        "likeThis" : likeThis,
        "unlike" : unlike,
        "unlikeThis" : unlikeThis,
		"getTotalLikesForThisPage" : getTotalLikesForThisPage,
		"getTotalLikes" : getTotalLikes,
		"isThisLiked" : isThisLiked,
		"isLiked" : isLiked,
		"setThisLiked" : setThisLiked,
		"setLiked" : setLiked		
    };
    
    $.PercLikedService = PercLikedService;
})(jQuery);

/**
 * Page list service, makes a call to the server and gets the page list entries.
 */
(function($)
{
    $.PercPageListService = {
        getPageEntries : getPageEntries
    };
    function getPageEntries(queryString, callback)
    {
        var serviceUrl = "http://www.cuidadodesalud.gov/enes/perc-metadata-services/metadata/get";
        $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data.results);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
    }
})(jQuery);/**
 * Page list service, makes a call to the server and gets the page list entries.
 */
(function($)
{
    $.PercResultService = {
        getPageEntries : getPageEntries
    };
    function getPageEntries(queryString, callback)
    {
        var serviceUrl = "http://www.cuidadodesalud.gov/enes/perc-metadata-services/metadata/get";
        return $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
    }
})(jQuery);/**
 * Page list service, makes a call to the server and gets the page list entries.
 */
(function($)
{
    $.PercTagListService = {
        getTagEntries : getTagEntries
    };
    function getTagEntries(queryString, orderBy, callback)
    {
        var paramOrderBy = "?sortTagsBy=" + orderBy;
        var serviceUrl = "/perc-metadata-services/metadata/tags/get" + paramOrderBy;
        $.PercServiceUtils.makeJsonRequest(serviceUrl,$.PercServiceUtils.TYPE_POST,true,function(status, results)
        {
            if(status == $.PercServiceUtils.STATUS_SUCCESS){
                callback(true,results.data);
            }
            else{
              var defMsg = $.PercServiceUtils.extractDefaultErrorMessage(results.request);
              callback(false, defMsg);
            }
            
        }, queryString);
    }
})(jQuery);/**
  * The delivery side archive list view class. Makes a call to the service to get page list entries and renders them in the
 * list.
 * On document ready loops through all perc-archive-list elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing.
 */
(function($)
{
    $(document).ready(function(){
        $.PercArchiveListView.updateArchiveList();
    });
    $.PercArchiveListView = {
        updateArchiveList : updateArchiveList
    };
    function updateArchiveList()
    {
        $(".perc-archive-list").each(function(){
            var currentArchiveList = $(this);
            if (currentArchiveList.attr("data") === "") return;  // CML-3830 Fixes attempted eval of empty value.
            var queryString = eval("(" + currentArchiveList.attr("data") + ")");


            //Get the render option: hierarchical or flat
            var renderOption = queryString.layout;
            delete queryString.layout;

            //Get the result page to display the pages for each tag
            var pageResult = queryString.archive_page_result;
            delete queryString.archive_page_result;

            //Get the number of entries to be shown
            var numberEntries = queryString.numberEntries;
            delete queryString.numberEntries;

            //Set the base URL to create the href for each item then
            var baseURL = window.location.protocol + '//' + window.location.host;
            var strJSON = JSON.stringify(queryString);

           $.PercArchiveListService.getArchiveEntries(queryString, function(status, archiveList){
                if(status)
                {
                    if(typeof(numberEntries) != "undefined" && numberEntries != "")
                    {
                        var itemToList =  numberEntries;
                    }
                    else
                    {
                        itemToList = archiveList.years.length;
                    }
                    if (typeof(renderOption) != "undefined" && renderOption != "" && renderOption == "perc-list-hierarchical")
                    {

                        for (var i = 0; i < itemToList; i++) {
                            var row = archiveList.years[i];

                            var ul = $("<ul>").addClass("perc-archive-list-wrapper perc-archive-hierarchical");

                            // If year don't have any entry - skip the year
                            if(row.yearCount > 0)
                            {
                                //Create the li element for the year
                                var linkYearText = row.year + " (" + row.yearCount + ")";

                                //Set the link for the item Year
                                var yearParam1 = row.year + "-01-01 00:00:00";
                                var yearParam2 = row.year + "-12-31 00:00:00";
                                if (typeof(pageResult) != "undefined" && pageResult != "")
                                {
                                    var query = eval("(" + strJSON + ")");
                                    query.criteria.push("dcterms:created >= '" + yearParam1 + "'");
                                    query.criteria.push("dcterms:created <= '" + yearParam2 + "'");
                                    var encodedQuery =  "&query=" + encodeURIComponent(JSON.stringify(query));
                                    var anchorYear = $("<a>")
                                        .attr("href", baseURL + pageResult + "?filter="+  row.year + encodedQuery)
                                        .text(linkYearText)
                                        .addClass("perc-archive-year-link");
                                }
                                else{
                                   var anchorYear = $("<a href='#'>")
                                        .text(linkYearText)
                                        .addClass("perc-archive-year-link");
                                }

                                ul.append(anchorYear);

                                //JSON object return the list of months in desc order, starting with December
                                var monthIndex = 12;

                                if(i == 0)
                                {
                                    monthIndex = archiveList.years[i].months.length;
                                }

                                for (var j = 0; j < archiveList.years[i].months.length; j++) {
                                    var row2 = archiveList.years[i].months[j];

                                    if(row2.count < 1)
                                    {
                                        monthIndex = monthIndex - 1;
                                        continue;
                                    }
                                    //Generate the param date to be passed as part of the query criteria
                                    var stringMonthParam = "";
                                    if (monthIndex >= 10)
                                    {
                                        stringMonthParam = monthIndex;
                                    }
                                    else
                                    {
                                        stringMonthParam = "0" + monthIndex;
                                    }
                                    var daysInMonth = new Date(row.year, monthIndex, 0).getDate();
                                    var dateParam1 = row.year + "-"+ stringMonthParam + "-01 00:00:00";
                                    var dateParam2 = row.year + "-"+ stringMonthParam + "-" + daysInMonth + " 00:00:00";

                                    //Generate the link text
                                    var linkText = row2.month + " (" + row2.count + ")";

                                    //Decrease the counter for the monthIndex
                                    if (monthIndex > 0)
                                    {
                                        monthIndex = monthIndex - 1;
                                    }

                                    //Set the link for the item month
                                    if (typeof(pageResult) != "undefined" && pageResult != "")
                                    {
                                        var query = eval("(" + strJSON + ")");
                                        query.criteria.push("dcterms:created >= '" + dateParam1 + "'");
                                        query.criteria.push("dcterms:created <= '" + dateParam2 + "'");
                                        var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query));
                                           var a = $("<a>")
                                                .attr("href", baseURL + pageResult + "?filter="+  row2.month + " " + row.year + encodedQuery)
                                                .text(linkText)
                                                .addClass("perc-archive-month-link");
                                    }
                                    else{
                                       var a = $("<a href='#'>")
                                                 .text(linkText)
                                                 .addClass("perc-archive-month-link");
                                    }
                                    var li = $("<li>")
                                            .addClass("perc-archive-month")                                            
                                            .append(a);
                                            li.hide();
                                    ul.append(li);
                                }

                            }
                            else {

                            }
                            currentArchiveList.find(".perc-archive-list-container").append(ul);

                        }


                    }
                    else {
                        //Set the variable if max entry value is defined
                        if(typeof(numberEntries) != "undefined" && numberEntries != "")
                        {
                            var flatItemToList =  numberEntries;
                        }
                        var listCounter = 0;
                        var ul = $("<ul>").addClass(".perc-archive-list-wrapper perc-archive-flat");
                        for (var i = 0; i < archiveList.years.length; i++) {

                            var row = archiveList.years[i];
                            //JSON object return the list of months in desc order, starting with December

                            var monthIndex = 12;
                            if(i == 0)
                            {
                                monthIndex = archiveList.years[i].months.length;
                            }

                            for (var j = 0; j < monthIndex; j++)
                            {

                                var row2 = archiveList.years[i].months[j];


                                if(row2.count < 1)
                                {
                                    //monthIndex = monthIndex - 1;
                                    continue;
                                }
                                //Generate the param date to be passed as part of the query criteria
                                var stringMonthParam = "";
                                if (monthIndex >= 10)
                                {
                                    stringMonthParam = monthIndex;
                                }
                                else
                                {
                                    stringMonthParam = "0" + monthIndex;
                                }
                                var daysInMonth = new Date(row.year, monthIndex, 0).getDate();
                                var dateParam1 = row.year + "-"+ stringMonthParam + "-01 00:00:00";
                                var dateParam2 = row.year + "-"+ stringMonthParam + "-" + daysInMonth + " 00:00:00";

                                //Generate the link text
                                var linkText = row2.month + " " + row.year + " (" + row2.count + ")";

                                //Decrease the counter for the monthIndex
                                if (monthIndex > 0)
                                {
                                    monthIndex = monthIndex - 1;
                                }

                                //Set the link for the item month
                                if (typeof(pageResult) != "undefined" && pageResult != "")
                                {
                                    var query = eval("(" + strJSON + ")");
                                    query.criteria.push("dcterms:created >= '" + dateParam1 + "'");
                                    query.criteria.push("dcterms:created <= '" + dateParam2 + "'");
                                    var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query));
                                       var a = $("<a>")
                                            .attr("href", baseURL + pageResult + "?filter="+ row2.month + " " + row.year + encodedQuery)
                                            .text(linkText)
                                }
                                else{
                                   var a = $("<a href = '#'>")
                                             .text(linkText)
                                }
                                var li = $("<li>")
                                        .addClass("perc-archive-month")
                                        .append(a);
                                ul.append(li);
                                //Increment the max value counter by 1.
                                listCounter = listCounter + 1;

                                //If max value is defined and counter is equal to max value break the month loop
                                if(listCounter == flatItemToList && typeof(numberEntries) != "undefined" && numberEntries != "")
                                {
                                    break;
                                }

                            }
                                //If max value is defined and counter is equal to max value break the year loop
                                if(listCounter == flatItemToList && typeof(numberEntries) != "undefined" && numberEntries != "")
                                {
                                    break;
                                }
                        }
                        currentArchiveList.find(".perc-archive-list-container").html("").append(ul);
                    }
                }
                else
                {
                    //Log the error and leave the original list entries as is
                }
            });

        });
    }
})(jQuery);

/* The delivery side blog list view class. Makes a call to the service to get blog list entries and renders them in the
 * list.
 * On document ready loops through all perc-blog-list-container elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing. Otherwise loops through the each blog list entry and creates a li element for each and appends it to
 * the list main element.
 */
(function($)
{
    $(document).ready(function(){
        $.PercBlogListView.updatePageLists();
    });
    $.PercBlogListView = {
        updatePageLists : updatePageLists
    };
    
    /**
     * Load a new set of blogs from the server, the call the callback function.
     * @name loadResults
     * @param target The element to load blog posts for.  Should contain a jQuery data object with the metadata query.
     * @param startPage The page number to load.  If 1 is passed, will update totalPages as well.
     * @param callback Which function to call after loading new blog posts.
     * @param callbackOptions Options to pass to the callback function.
     */
    function loadResults(target, startPage, callback, callbackOptions)
    {
        var currentBlogList = $(target);
        if(!target.settings)
        {
            target.settings = {};
        }
        //var queryString = $.parseJSON(currentBlogList.attr("data"));
        
        // Get maxResults for later use, and set start index for the query.  Index is 0-based post number.
        if(isNaN(target.settings.maxResults) || target.settings.maxResults < 1)
                target.settings.maxResults = 0;
        
        target.settings.query.maxResults = target.settings.maxResults;
        target.settings.query.startIndex = (target.settings.maxResults*(startPage - 1));
        target.settings.baseURL = window.location.protocol + '//' + window.location.host + window.location.pathname
        
        // Adding the filters from the query
        var urlstring = $.deparam.querystring();
        if (urlstring.query != undefined && urlstring.filter != undefined)
        {
            try
            {
                var obj = $.parseJSON(urlstring.query);
                
                $.map(obj.criteria, function(n, i){
                    if ($.inArray(n, target.settings.query.criteria) == -1)
                    {
                        target.settings.query.criteria[target.settings.query.criteria.length] = n;
                    }
                });
            }
            catch(e){}  // If criteria json is not well formed, then do nothing
        }
        
        return $.PercBlogListService.getPageEntries(target.settings.query, function(status, results){
            if(status)
                callback(target, results, callbackOptions);
            else
            {
                if(console)
                    console.log(results);
            }
        });
        
    }
    // Pre-processing on the results before handing it off to the display function
    function doResultsDisplay(target, returnData, options){
        // We only get totalEntries back if we've requested page 1.  Otherwise, we get null.
        if (returnData.totalEntries)
        {
            target.totalPages = Math.ceil(returnData.totalEntries/target.settings.maxResults);
            target.totalEntries = returnData.totalEntries;
        }
            
        var results = returnData.results;
        displayResults(target, results, options);
    }
    /**
     * Given a list of blog posts, display them.
     * @name displayResults
     * @param target The element to display the blog posts inside of.
     * @param pageEntries The list of blog posts.
     * @param options Options passed through to the function.
     */
    function displayResults(target, pageEntries, options){
        var currentBlogList = $(target);
        
        if(!(pageEntries.length > 0)) // Inverted logic to catch NaNs.
        {
            currentBlogList.find('.perc-no-post').show();

        }
        else
        {
            currentBlogList.find('.perc-no-post').hide();
        }
        var urlstring = $.deparam.querystring();
        
        if (urlstring.query != undefined && urlstring.filter != undefined)
        {
            try
            {

                $.parseJSON(urlstring.query);
                var objData = $.parseJSON(currentBlogList.attr("titleData"));
                
                if(urlstring.title != undefined)
              	{
              		objData.resultsTitle = urlstring.title;
              	}	
                currentBlogList.parent().find(".perc-result-divider").remove();
                currentBlogList.parent().find(".perc-bloglist-result-container").remove();                
                currentBlogList.parent().prepend("<div class=\"perc-result-divider\"></div>").prepend(createTitleHtml(objData.resultsTitle, urlstring.filter, target.totalEntries));
                var titleValue = urlstring.filter;
                setTimeout(function(){updateTitleValue(titleValue)}, 100);
		}
            catch(e){
      			console.log(e);
            }
        }        

        //Get the structure and get the root element of the list
        var listRoot = currentBlogList.find(".perc-blog-list-structure .perc-blog-list");
        //Clone the li for future use
        var listElem = listRoot.find("li").clone();
        //Empty the root element and add it to the actual list root.
        listRoot = listRoot.clone().empty();
        currentBlogList.find(".perc-blog-list").not('.perc-blog-list-structure .perc-blog-list').empty().remove();
        scroll(0,0);
        currentBlogList.prepend(listRoot);
        //Loop through the page entries and build the new list element as per the structure.
        //Then add the newly created element to the list root.
        for(var i=0;i<pageEntries.length;i++)
        {
            var pageEntry = pageEntries[i];
            var newListElem = listElem.clone();
            var rowClass = i % 2 === 0 ? "perc-list-even":"perc-list-odd";
            var spClass = i === 0?"perc-list-first":i===pageEntries.length-1?"perc-list-last":"";
            newListElem.addClass(rowClass);
            if(spClass !== "")
                newListElem.addClass(spClass);
            var summary = null;
            var author = null;
            var postDate = null;
            var tagList = null;
            var categoryList = null;
            $.each(pageEntry.properties, function(key,value){
                if(key == "dcterms:abstract")
                {
                    summary = value.replace('<A xmlns="http://www.w3.org/1999/xhtml" xmlns:perc="http://percussion.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" class="perc-more-link"></A>', "&nbsp;" + $('<a />').attr('class', 'perc-no-update-link-text perc-blog-list-more-link').attr('href', pageEntry.folder + pageEntry.name).text(target.settings.blogPostReadMoreText).appendTo('<div />').parent().html());
                }
                else if(key == "dcterms:author")
                {
                    author = value;
                }
                else if(key == "dcterms:created")
                {
                    // dcterms:created comes down in an ISO format, which we don't natively have the tools to parse.
                    // $.timeago.parse sends back a Date object given that format.
                    postDate = target.settings.dateFormatter.format($.timeago.parse(value));
                }
                else if(key == "perc:tags")
                {
                    tagList = processTags(value, target);
                }
                else if(key == "perc:category")
                {
                    categoryList = processCategories(value, target);
                }
                
            });
            var linkText = pageEntry.linktext;
            // First change all the href's for the links, the remove the ones which shouldn't have their innerHtml updated, then update innerHtml on everything that should.
            newListElem.find("a").attr("href",pageEntry.folder + pageEntry.name).not('.perc-no-update-link-text').html(linkText);
            
            if(summary)
                newListElem.find("div.perc-blog-list-summary-container").prepend(summary);
                
            if(tagList)
                newListElem.find("div.perc-blog-list-tag-container").append(tagList);
            else
                newListElem.find("div.perc-blog-list-tag-container").addClass('perc-blog-hide-container');
                
            if(categoryList)
                newListElem.find("div.perc-blog-list-category-container").append(categoryList);
            else
                newListElem.find("div.perc-blog-list-category-container").addClass('perc-blog-hide-container');
                
            if(author)
                newListElem.find(".perc-blog-list-byline-container").text("by " + author);
                
            if(postDate)
                newListElem.find(".perc-blog-list-date-container").text(postDate);
                
            listRoot.append(newListElem);
            currentBlogList.find('.perc-list-main-wrapper').append(listRoot);
        }
    }
    
    function processTags(list, target){
        if (typeof(list) == "string")
            list = list.split()
        list = list.sort()
        var strReturn = "";
        for(i=0;i<list.length;i++){
            var jsonQuery = {'criteria':["perc:tags = '" + list[i] + "'"]}
            var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery));
            sep = (i == list.length-1)? "": ",";
            strReturn += '<a href="' + target.settings.baseURL + "?filter=" + list[i] + encodedQuery + '&title=Tags%3A%20">' + list[i] + sep + ' </a>'
        }
        return strReturn;
    }
    
    function processCategories(list, target){
        if (typeof(list) == "string")
            list = list.split()
        list = list.sort(orderCategory)
        strReturn = "";
        for(i=0;i<list.length;i++){
            var jsonQuery = {'criteria':["perc:category = '" + list[i] + "'"]}
            var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery));
            sep = (i == list.length-1)? "": ",";
            cat = list[i].split('/');
            strReturn += '<a href="' + target.settings.baseURL + "?filter=" + cat[cat.length-1] + encodedQuery + '&title=Categories%3A%20">' + cat[cat.length-1]  + sep + ' </a>';
        }
        return strReturn;
    }
    
    function orderCategory(a, b){
        a = a.split('/');
        b = b.split('/');
        if(a[a.length-1] == b[b.length-1])
            return 0;
        if(a[a.length-1] > b[b.length-1])
            return 1;
        return -1
    }

    function createTitleHtml(resultsTitle, filter, count)
    {
        var title = $("<div/>")
                    .append($("<h2/>")
                            .addClass("perc-bloglist-result-title")
                            .text(resultsTitle)
                            .append($("<span class='perc-results-title-value'></span>").text(filter))
                            .append($("<span/>")
                                .addClass("perc-bloglist-result-count")
                                   .text(count + (count==1?" entry":" entries"))
                                )
                    ).addClass("perc-bloglist-result-container");
        return title;
    }
    function updateTitleValue(titleValue)
    {
		try
		{
			var transUrl = "/enes/perc-common-ui/jsp/CreateJsonPair.jsp?paramName=title&paramValue=" + encodeURIComponent(titleValue);
			$.ajax({
			  url: transUrl,
			  success: function(data){
				try
				{
					var titleData = $.parseJSON(data);
					var tagTitle = titleData.title;
					$(".perc-results-title-value").text(tagTitle);
				}
				catch(err)
				{
				}
			  }
			});
		}
		catch(err)
		{
		}
	}
    
    function updatePageLists(){
        $(".perc-blog-list-container").each(function(){
            var callback = function(target, navLoc, options){
                // Needs to know about doResultsDisplay.
                return loadResults(target, navLoc, doResultsDisplay, options);
            };
            
            // Sane defaults
            this.settings = {
                criteria: [],
                dateformat: "EEE MMM d, yyyy 'at' hh:mm a", // looks like Tuesday, March 15, 2011 at 11:11 PM
                dateFormatter: null,
                summary: null,
                filter: null,
                query: {},
                blogPostReadMoreText: 'more...',
                pagingPagesText: 'pages',
                pagingOfText: 'of',
                totalEntries:0
            };
            
            var callbackOptions = {};
            this.settings.query.criteria = [];
            $.extend(this.settings, $.parseJSON($(this).attr('data')));
            
            this.settings.query = $.extend(true, {}, this.settings.query); // Prevents overwrite of anything, but initializes it if it doesn't exist already.
            this.settings.query.criteria = this.settings.query.criteria.concat(this.settings.criteria);
            this.settings.query.maxResults = this.settings.maxResults;
            if(typeof(this.settings.orderBy) != "undefined")
            {
                this.settings.query.orderBy = this.settings.orderBy;
            }
            // Get date format, and build a date formatter, to be used in the display.
            this.settings.dateFormatter = new SimpleDateFormat(this.settings.dateFormat);
            $(this).PercResultsPaging(callback, callbackOptions);
        });
    }
})(jQuery);/**
 * The delivery side blog post view class. Makes a call to the service to get previous and next blog post entries and renders them as
 * part of blog post navigation
 * On document ready loops through all perc-blog-navigation-container elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing. 
 */
(function($)
{
    $(document).ready(function(){
        $.PercBlogPostView.updateBlogNav();
        $.PercBlogPostView.updateBlogLink();
    });
    $.PercBlogPostView = {
        updateBlogNav : updateBlogNav,
        updateBlogLink : updateBlogLink
    };
    
    function updateBlogNav()
    {
        $(".perc-blog-navigation-container").each(function(){
            var currentBlogNavigation = $(".perc-blog");
            if (currentBlogNavigation.attr("data") === "") return;  // CML-3830 Fixes attempted eval of empty value.
            var queryString = eval("(" + currentBlogNavigation.attr("data") + ")");
            
            //Get the blog nav context style
            var blogNavText = queryString.navType;
            delete queryString.navType;
            var blogPostNextPost = queryString.blogPostNextPost;
            delete queryString.blogPostNextPost;
            var blogPostPrePost = queryString.blogPostPrePost;
            delete queryString.blogPostPrePost;
                        
            //Get folder path and attach the pagename to make a complete pagepath.
            var folderPath = queryString.folderPath;
            delete queryString.folderPath;
            
            //Get the site name
            var siteName = queryString.siteName;
            delete queryString.siteName;
              
            var pagePath = '/' + siteName + window.location.pathname;

            if(!(queryString.criteria.length))
            {
                $('.perc-blog').find('.perc-blog-nav-left-wrapper , .perc-blog-nav-right-wrapper').hide();
                return;
            }
            $.PercBlogPostService.getPostNavEntries(queryString, pagePath, function(status, navEntries){
                if(status)
                {
                    //If we have the next entry set the anchor href and if the setting is blogTitle set the link text also
                    if(navEntries.next != null) 
                    {
                        $('.perc-newer-post-wrapper a').attr('href', navEntries.next.pagepath.replace("/" + siteName, "")); 
                        if(blogNavText == "blogTitle")
                        {
                            $('.perc-newer-post-title').text(navEntries.next.linktext);
                        }
                        else
                        {
                            $('.perc-newer-post').text(blogPostNextPost);
                        }
                    }
                    else
                    {
                        //We don't have the next entry simply hide the div
                        $('.perc-blog').find('.perc-blog-nav-right-wrapper').html("&nbsp;");
                    }
                    //If we have the previous entry set the anchor href and if the setting is blogTitle set the link text also
                    if(navEntries.previous != null)
                    {
                        $('.perc-older-post-wrapper a').attr('href', navEntries.previous.pagepath.replace("/" + siteName, "")); 
                        if(blogNavText == "blogTitle")
                        {
                            $('.perc-older-post-title').text(navEntries.previous.linktext);
                        }
                        else
                        {
                            $('.perc-older-post').text(blogPostPrePost);
                        }
                    }    
                    else
                    {
                        //We don't have the previous entry simply hide the div
                        $('.perc-blog').find('.perc-blog-nav-left-wrapper').html("&nbsp;");
                    }
                }
                else
                {
                    $('.perc-blog').find('.perc-blog-nav-left-wrapper , .perc-blog-nav-right-wrapper').hide();

                }
            }); 
        
        });
    }
    
    function updateBlogLink()
    {
        $('.perc-blog-wrapper').each(function(){
            var currentBlogNavigation = $(".perc-blog");
            if (currentBlogNavigation.attr("data") === "") return;  // CML-3830 Fixes attempted eval of empty value.
            var queryString = eval("(" + currentBlogNavigation.attr("data") + ")");
            // Get the blog index page
            var siteName = "/" + queryString.siteName;
            var folderPath = queryString.folderPath;
            var blogIndexPage = folderPath.replace(siteName, "") + "/index";
            
            // Tags
            $('.perc-blog-post-tag-container').find('a').each(function(){
                var tag = ($.trim($(this).html())).replace(",", "");
                var jsonQuery = {'criteria':["perc:tags LIKE '" + tag + "'"]}
                var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery));
                $(this).attr("href", blogIndexPage + "?filter="+ tag + encodedQuery);
            });
            
            // Categories
            $('.perc-blog-post-category-container').find('a').each(function(){
                var categoryPath = $(this).attr('data');
                var category = ($.trim($(this).html())).replace(",", "");
                var jsonQuery = {'criteria':["perc:category LIKE '" + categoryPath + "'"]}
                var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(jsonQuery));
                $(this).attr("href", blogIndexPage + "?filter="+ category + encodedQuery);
            });
        });
    }
})(jQuery);
/**
 * The delivery side category list view class. Makes a call to the service to get page list entries
 * and renders them in the list.
 * On document ready loops through all perc-category-list elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing.
 */
var strJSON;
var pageResult;
var baseURL;
(function($)
{
    $(document).ready(function(){
        $.PercCategoryListView.updateCategoryList();
    });
    $.PercCategoryListView = {
        updateCategoryList : updateCategoryList
    };
    var pageResult;
    var baseURL;
    var strJSON;
    var nRow;
    function updateCategoryList()
    {
        $(".perc-category-list").each(function(){
            var currentCategoryList = $(this);
            var queryString = eval("(" + currentCategoryList.attr("data") + ")");
            //Get the result page to display the pages for each category
            pageResult = queryString.category_page_result;
            delete queryString.category_page_result;
            //Get the display option (Expanded hierarchy = "expanded", Collapsible = "collapsible")
            var display = queryString.display_option;
            delete queryString.display_option;
            //Get the max categories option
            //set the tree pre-expanded level for Collapsible option or the row number to show for the Expanded hierarchy
            var maxCat = queryString.max_categories;
            delete queryString.max_categories;
            //Set the base URL to create the href for each category then
            baseURL = window.location.protocol + '//' + window.location.host;
            strJSON = JSON.stringify(queryString);
            nRow = 0
            $.PercCategoryListService.getCategories(queryString, function(status, categoryEntries){

                //Clean the HTML generated by CM1
                currentCategoryList.find(".perc-category-list-expanded,.perc-category-list-collapsible").empty();
                //Destroy previous element of Dynatree.
                currentCategoryList.find(".perc-category-list-collapsible").dynatree("destroy");
                if(status && categoryEntries.length > 0)
                {
                    var ul = "";
                    if (display == "collapsible")
                    {
                        //Collapsible Tree
                        ul = createNestedUlLiTreeCollapsible(categoryEntries, "", maxCat, 0);
                        currentCategoryList.find(".perc-category-list-collapsible").html(ul)
                        currentCategoryList.find(".perc-category-list-collapsible").dynatree({
                            minExpandLevel: 2,
                            onActivate : function(node){
                                    var href = $(node.data.title).attr("href");
                                    var count = $(node.data.title).attr("data");
                                    if(count>0)
                                        window.location.href = href;
                            }
                        })
                    }
                    else{
                        //Full expanded Tree
                        ul = createNestedUlLiTreeExpanded(categoryEntries, "", maxCat, 0);
                        currentCategoryList.find(".perc-category-list-expanded").html(ul)
                    }
                    
                    //currentCategoryList.find(".perc-category-list-expanded,.perc-category-list-collapsible").show();
                }
                else if(status && categoryEntries.length == 0)
                {
                    /*
                    $(".perc-category-list-static ul")
                        .dynatree({onActivate : function(node){
                            var href = $(node.data.title).attr("href");
                            window.location.href = href;
                        }});
                    */
                }
                else
                {
                    //Log the error and leave the original list entries as is
                }
            });
        });
    }
    
    //Generate the structure for the Full expanded tree using UL and LI
    function createNestedUlLiTreeExpanded(treeNodes, parentPath, maxRow, nLevel){
        var ul = "";
        if (maxRow > nRow || maxRow == 0){
            ul = $("<ul>")
                .addClass("perc-category-elements")
                .addClass("perc-category-level" + nLevel);
            for(var n=0; n<treeNodes.length; n++){
                if (maxRow > nRow || maxRow == 0){
                    var node = treeNodes[n];
                    node.path = parentPath + "/" + node.category;
                    var li = parseNode(node, nLevel)
                    ul.append(li);
                    nRow++;    
                    if(node.children && node.children.length > 0){
                        li.append(createNestedUlLiTreeExpanded(node.children, node.path, maxRow, nLevel + 1));
                    }
                }
            }
        }
        return ul;
    }
    
    //Generate the structure for the Collapsible Tree using dynaTree plugin
    function createNestedUlLiTreeCollapsible(treeNodes, parentPath, maxRow, nLevel){
        var ul = "";
        ul = $("<ul>")
            .addClass("perc-category-elements")
            .addClass("perc-category-level" + nLevel);
            
        for(var n=0; n<treeNodes.length; n++){
            if ((nLevel != 1 || n < maxRow) || maxRow == 0){
                var node = treeNodes[n];
                node.path = parentPath + "/" + node.category;
                var li = parseNode(node)
                ul.append(li);
                if(node.children && node.children.length > 0){
                    li.append(createNestedUlLiTreeCollapsible(node.children, node.path, maxRow, nLevel + 1));
                }
            }
        }
        return ul;
    }
    
    //Create the LI node from a Category.
    function parseNode(node){
        var countTotal = node.count.first + node.count.second;
        var nodeStr = node.category + " (" + countTotal + ")";
        var query = eval("(" + strJSON + ")");
        query.criteria.push("perc:category LIKE '" + node.path + "%'");
        var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query));
       
        var href = "#";
        if (typeof(pageResult) != "undefined" && pageResult != "")
            var href = baseURL + pageResult + "?filter="+ node.category + encodedQuery;
            
        var a = $("<a>")
            .attr("href", href)
            .attr("data", countTotal)
            .attr("title", nodeStr)
            .addClass("perc-node")
            .append(nodeStr);
        var li = $("<li>")
            .addClass("perc-category-element")
            .append(a);
        return li;
    }
    
})(jQuery);/******************************************************************************
 *
 * [ PercCommentsView.js ]
 * 
 * COPYRIGHT (c) 1999 - 2011 by Percussion Software, Inc., Woburn, MA USA.
 * All rights reserved. This material contains unpublished, copyrighted
 * work including confidential and proprietary information of Percussion.
 *
 * Used to render comments pulled from the comments service. Uses jsonp for
 * cross-domain communication to the comments service. 
 * 
 * Dependecies:
 *  - jquery.js  (1.3.2 or greater)
 *  - json.js 
 *  - jquery.jsonp-2.1.4.js (or greater) 
 *  - simpledateformat.js
 *  - jquery.timeago.js     
 ******************************************************************************/
(function($){
   
   $(document).ready(function(){
      //Change the form action url to point to the right server
      var baseURL = window.location.protocol + '//' + window.location.host;          
	  var url = baseURL + "/perc-comments-services/comment";		
	  $("form[name = 'commentForm']").attr("action", url);
   });
	
   var globals = {
      dateFormatter: null 
   }; 
   
   var settings = {
      serviceurl: null, // The comments service url. Expects no query string.
      site: null, // The sitename
      pagepath: null, 
      tag: null,
      username: null,
      state: 'APPROVED', // either APPROVED or REJECTED
      moderated: null,
      fields: ['createdDate', 'username', {name: 'title', element: 'h3'}, 'text'],
      /*
        The fields array property defines what fields will show and their sort order and containment.
        The following fields are available:
           createdDate,
           email,
           text,
           title,
           username
        
        Fields can be added to the array as a string or an object. The object version gives
        more advanced options as follows:
           {
              name: '', // The field name
              element: '', // The element to use to display field value, defaults to the defaultElement
              posttext: '', // Text to add after the field value
              pretext: ''  // Text to add before the field value
           }   
        
        Simple field containment can be defined by adding the desired fields with another array.
        Example:
           [['createdDate', 'username'], [{name: 'title', element: 'h3'}, 'text']],
           
           This will wrap containers around each sets of fields with each having classname
           as comment-container-n  where n is the positional containment index order.
           
           Containment is only supported one level deep and either all fields have a container or
           none should. No mixing is supported.           
           
      */
      defaultElement: 'div', // The default element used to build the comment structure
      defaultClass: "comments", // The default class added to the intial comments wrapper element
      dateformat: 'EEEE, MMMM d, yyyy hh:mm aa', // looks like Tuesday, March 15, 2011 11:11 PM 
      maxResults: 0, // Maximum returned results, 0 or less indicates return maximum possible
      sortby: 'CREATEDDATE', // sortby field, CREATEDATE, EMAIL, USERNAME
      ascending: true,
      lastCommentId: 0
   };
   
   // Publically exposed methods   
   var methods = {
      init: function(options){         
         return this.each(function(){
            if(options){ 
               $.extend( settings, options );
            }
            globals.dateFormatter = new SimpleDateFormat(settings.dateformat);
            var querystring = $.deparam.querystring();
            if (typeof(querystring.lastCommentId) != "undefined")
                settings.lastCommentId = querystring.lastCommentId;
         });
   
      },
      show: function(){
         var el = this;
         el.addClass(settings.defaultClass);          
         getComments(function(success, data){
            if(success && typeof(data.comments) != 'undefined' && data.comments != null)
            {
               var comments = data.comments; 
               for(c = 0; c < comments.length; c++)
               {
                  el.append(createCommentHtml(comments[c]));
                  el.append($("<div/>").addClass("comment-divider"));   
               }
            }
            else
            {
               // Error while attempting to retrieve data from service
               // Right now we just fail silently at some point we should try to log
               // somewhere
            }
            var lastComment = $('.comment-highlight')
            if (lastComment.position()){
                $('html,body').animate({ scrollTop : lastComment.position().top }, 200 )
            }
         });
      }   
   };
   // Define the plugin on the jQuery namespace
   $.fn.PercCommentsView = function(method)
   {     
      
      // Method calling logic
      if ( methods[method] ) {
        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
      } else if ( typeof method === 'object' || ! method ) {
        return methods.init.apply( this, arguments );
      } else {
        $.error( 'Method ' +  method + ' does not exist on jQuery.PercCommentsRenderer' );
      }      
   }
   
   // Private Methods   
   
   /**
    * Calls the service to retrieve comment data. The call is via jsonp so
    * it is cross domain compatible.
    */           
   function getComments(callback)
   {
     $.jsonp({
         url: getUrl(),
         data: '',
         success: function(data, status){
            callback(true, data);
         },
         error: function(xOptions, error)
         {
           callback(false);
         }  
      }); 
             
   } 
   
   /**
    * Create the url from the specified settings.
    * @returns the url string, never <code>null</code> or empty.
    * @type string
    */               
   function getUrl()
   {
	  //Build a valid service URL based on the location and host
	  var baseURL = window.location.protocol + '//' + window.location.host;          
	  var url = baseURL + "/perc-comments-services/comment/jsonp";
	           
      url += "?site=" + settings.site;
      if(!isBlank(settings.pagepath))
         url += "&pagepath=" + settings.pagepath;
      if(!isBlank(settings.username))
         url += "&username=" + settings.username;
      if(!isBlank(settings.sortby))
      {
         url += "&sortby=" + settings.sortby;
         url += "&ascending=" + settings.ascending;
      }
      if(!isBlank(settings.tag))
         url += "&tag=" + settings.tag;
      if(!isBlank(settings.state))
         url += "&state=" + settings.state;   
      if(!isBlank(settings.moderated))
         url += "&moderated=" + settings.moderated;
      if(!isBlank(settings.lastCommentId))
         url += "&lastcommentid=" + settings.lastCommentId;
      url += "&callback=?"; //must be last
      return url;
   }
    
   /**
    * Create the comments html from the commentData returned from
    * the server. What fields are shown and their order is based on the 
    * data in the settings.fields array.
    * <pre>
    *  Produces the following HTML:
    *  &lt;div class=&quot;comments&quot;>
    *        &lt;div class=&quot;comment&quot;>
    *           &lt;div class=&quot;comment-createdDate&quot;>Friday, February 18, 2011 01:30 PM ET&lt;/div>
    *           &lt;h4 class=&quot;comment-username&quot;>&lt;a href=&quot;http://www.mysite.com&quot;>Bob&lt;/a>&lt;/h4>
    *            
    *           &lt;h3 class=&quot;comment-title&quot;>The comment Title&lt;/h3>              
    *           &lt;div class=&quot;comment-text&quot;>
    *           The comment Text
    *           &lt;/div>               
    *        &lt;/div>
    *        &lt;div class=&quot;comment-divider&quot;>&lt;/div>
    *        &lt;div class=&quot;comment&quot;>
    *           &lt;div class=&quot;comment-createdDate&quot;>Friday, February 18, 2011 01:30 PM ET&lt;/div>
    *           &lt;h4 class=&quot;comment-username&quot;>&lt;a href=&quot;http://www.mysite.com&quot;>Sal&lt;/a>&lt;/h4>              
    *           &lt;h3 class=&quot;comment-title&quot;>The comment Title&lt;/h3>               
    *           &lt;div class=&quot;comment-text&quot;>
    *           The comment Text
    *           &lt;/div>              
    *        &lt;/div>
    *         &lt;div class=&quot;comment-divider&quot;>&lt;/div>
    *     &lt;/div>    
    * </pre>            
    * @param commentData a single comments data object, assumed not
    * <code>null</code>.
    * @returns a jQuery object that represents the comment HTML. Never
    * <code>null</code> or empty.
    * @type jQuery    
    */                               
    function createCommentHtml(commentData)
    {
        var comment = $("<div/>").addClass('comment');
        var field = null;
        var snippet = null;
        if(settings.fields.length > 0)
        { 
            //Highlight the recent comment
            if(settings.lastCommentId == commentData["id"]){
                comment.addClass('comment-highlight');
                if (!commentData["moderated"] && commentData["approvalState"] != "APPROVED")
                {
                    comment.append(
                            $("<div/>")
                            .addClass('comment-message')
                            .text('Your comment is being held for moderation. It will appear on this page after approval.')
                    )
                }
            }
            if($.isArray(settings.fields[0]))
            {
                // Use comment containers
                for(z = 0; z < settings.fields.length; z++)
                {
                   var container = $("<div/>").addClass("comment-container-" + z);
                   comment.append(container);
                   createFieldsHtml(commentData, settings.fields[z], container);
                }
            }
            else
            {
               createFieldsHtml(commentData, settings.fields, comment);
            }
        }
        return comment;   
    }
   
   /**
    * Creates the field html.
    * @param commentData {object}, the data for the individual comment brought back
    * from the service. Assumed not <code>null</code>.    
    * @param fields {array} the field configuration array. Assumed not
    * <code>null</code>.    
    * @param container {jQuery} the container to append the html to,
    * assumed not <code>null</code>.    
    */                   
    function createFieldsHtml(commentData, fields, container)
    {
       for(i = 0; i < fields.length; i++)
       {
         field = getFieldObject(fields[i]);
         if(isBlank(commentData[field["name"]]))
            continue;
         snippet = $("<" + field["element"] + "/>")
             .addClass("comment-" + field["name"])
             .html(commentData[field["name"]]);
          if(field["name"] == 'username' && !(isBlank(commentData.url)))
          {
             var urlSnippet = $("<a/>")
                .addClass("comment-url")
                .attr("href", commentData["url"])
                .text(snippet.text());
             snippet.text("");
             snippet.append(urlSnippet);   
          }
          if(field["name"] == 'createdDate')
          {
              var dt = $.timeago.parse(commentData["createdDate"]);
              snippet.text(globals.dateFormatter.format(dt));
          }   
          // Handle pretext
          if(!(isBlank(field.pretext)))
          {
             snippet.html("<span class='comment-pretext'>" + field["pretext"] + "</span>" + snippet.html());
          }
          // Handle posttext
          if(!(isBlank(field.posttext)))
          {
             snippet.html(snippet.html() + "<span class='comment-posttext'>" + field["posttext"] + "</span>");
          }
          container.append(snippet);   
       }
    }
   
   /**
    * Helper method to always return a field object with a specified element, even
    * if the string field shortcut is used.
    * @param rawField {Object or String}
    */               
   function getFieldObject(rawField)
   {
      if(typeof(rawField) == 'object')
      {
         if(isBlank(rawField.element))
            rawField["element"] = settings.defaultElement;
         return rawField;
      }
     return {name: rawField, element: settings.defaultElement};
   }
   
   /**
    * Helper method to determine if a string object is defined and not empty.
    * @param obj {object} the string object, may be <code>null</code>, empty
    * or <code>undefined</code>.
    * @returns <code>true</code> if the string is considered blank.
    * @type boolean                
    */       
   function isBlank(obj)
   {
      return typeof(obj) == 'undefined' || obj == null || obj.length <= 0;
   }
   
   /* Auto bind comments instances for existing elements that have
    * a class of <code>perc-comments-view</code>.
    */
   $(document).ready(function(){
       $('.perc-comments-view').each(function(){
          var $el = $(this);
          var data = $el.attr("data");
          if(typeof(data) == 'string' && data.length > 0)
          {
              var obj = null;
              eval("obj = " + data +";");
              //Look for the special finderpath that only comes from a cm1 call to this plugin
              //from that we set the site and pagepage
              if(typeof(obj.finderpath))
              {
                  var tmp = obj.finderpath.split("/");
                  obj.site = tmp[2];
                  obj.pagepath = "/" + tmp.slice(3).join("/");  
              }
              $el.PercCommentsView(obj);
              $el.PercCommentsView('show');
          }
       });
       
   });
   
})(jQuery);

/**
 * PercLikedView.js
 * @author Wesley Hirsch
 * @author Jose Annunziato
 * View for LikedWidget.
 * Contains functionality which allows the LikedWidget to recive clicks, and react to them.
 * 
 * (*) Dependencies
 * PercLikedService.js
 * 
 */

(function($){
    
    // constants
    var LIKED = 1, UNLIKED = 0, PAGE_TYPE = "page";
    var PERC_LIKE_WIDGET = ".perc-like-widget";
    var PERC_LIKE_TOTAL_LIKES = ".perc-like-total-likes";
    var PERC_UNLIKED = "perc-unliked";
    var PERC_LIKED = "perc-liked";
    
    // state variables
    var likePageUrl;
    var likeSite;
    var likePathname;
    var likeTotalLikes;
    var likeCookieState;
    //var likeService = $.PercLikedService;
    var likeWidgetState = UNLIKED;
    
    function initLikeWidget() {
    
        if (!($(PERC_LIKE_WIDGET).length )) {
            return;
        } 
        var siteName = $(PERC_LIKE_WIDGET).attr("siteName");
        
        $.PercLikedService.siteName = siteName;
        
        if($.PercLikedService.isThisLiked())
            likeWidgetState = LIKED;
        else
            likeWidgetState = UNLIKED;
        
        $.PercLikedService.getTotalLikesForThisPage(function(success, data){
            if(success) {
                likeTotalLikes = data.totalLikes;
            }
			else {
				if(likeWidgetState === LIKED) {
					likeTotalLikes = 1;
				}
				else {
					likeTotalLikes = 0;
				}
			}
            renderLikeWidget(likeWidgetState, likeTotalLikes);
        });
    }
    
    function renderLikeWidget(likeWidgetState, likeTotalLikes) {
		if(typeof(likeWidgetState) === "undefined" || likeWidgetState === null) return false;
		if(typeof(likeTotalLikes) === "undefined" || likeTotalLikes === null) return false;
		
        var likeWidget = $(PERC_LIKE_WIDGET);
        var button = likeWidget.find("button");
        button.blur();
        var totalLikes = likeWidget.find(PERC_LIKE_TOTAL_LIKES);
        if(likeWidgetState === LIKED) {
            likeWidget
                .removeClass(PERC_UNLIKED)
                .addClass(PERC_LIKED);
            button
                .unbind().click(unlike)
                .attr("title", "Remove");
        } else {
            likeWidget
                .removeClass(PERC_LIKED)
                .addClass(PERC_UNLIKED);
            button
                .unbind().click(like)
                .attr("title", "Like");
        }
        if(likeTotalLikes != 0 && likeTotalLikes != undefined && likeTotalLikes != null) {
            $(".perc-like-counter").show();
            totalLikes.text(likeTotalLikes);
			if(likeTotalLikes == 1)
			{
				$('.perc-like-people').text('person');
			}
			else
			{
				$('.perc-like-people').text('people');
			}
        } else {
            $(".perc-like-counter").hide();
        }
    }
    
    function like() {
        likeWidgetState = LIKED;
        $.PercLikedService.likeThis(function(success, data){
            if(success) {
                likeTotalLikes = data.totalLikes;
            } else {
				if(likeWidgetState === LIKED)
					likeTotalLikes = 1;
				else
					likeTotalLikes = 0;
            }
            renderLikeWidget(likeWidgetState, likeTotalLikes);
        });
    }
    
    function unlike() {
        $.PercLikedService.unlikeThis(function(success, data){
			if(success){
				likeWidgetState = UNLIKED;
				likeTotalLikes = data.totalLikes;
			} else {
				if(likeWidgetState === LIKED)
					likeTotalLikes = 1;
				else
					likeTotalLikes = 0;
			}
				
            renderLikeWidget(likeWidgetState, likeTotalLikes);
        });
    }
    
    $(document).ready(function(){
        initLikeWidget();
    });
})(jQuery);

/**
 * The delivery side page list view class. Makes a call to the service to get page list entries and renders them in the
 * list.
 * On document ready loops through all perc-page-auto-list elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing. Otherwise loops through the each page list entry and creates a li element for each and appends it to
 * the list main element.
 */
(function($)
{
    $(document).ready(function(){
        $.PercPageListView.updatePageList();
    });
    $.PercPageListView = {
        updatePageList : updatePageList
    };
    function updatePageList()
    {
        $(".perc-page-auto-list").each(function(){
            var currentAutoList = $(this);
			if (currentAutoList.attr("data") === "") return;  // CML-3830 Fixes attempted eval of empty value.
            var queryString = eval("(" + currentAutoList.attr("data") + ")");
            $.PercPageListService.getPageEntries(queryString, function(status, pageEntries){
                if(status)
                {
                    //Get the structure and get the root element of the list
                    var listRoot = currentAutoList.find(".perc-page-auto-list-structure .perc-list-main");
                    //Clone the li for future use
                    var listElem = listRoot.find("li").clone();
                    //Empty the root element and add it to the actual list root.
                    listRoot.empty();
                    currentAutoList.find(".perc-list-main").empty().remove();
                    currentAutoList.append(listRoot);
                    //Loop through the page entries and build the new list element as per the structure.
                    //Then add the newly created element to the list root.
                    for(i=0;i<pageEntries.length;i++)
                    {
                        var pageEntry = pageEntries[i];
                        var newListElem = listElem.clone();
                        var rowClass = i % 2 == 0 ? "perc-list-even":"perc-list-odd";
                        var spClass = i == 0?"perc-list-first":i==pageEntries.length-1?"perc-list-last":"";
                        newListElem.addClass(rowClass);
                        if(spClass != "")
                            newListElem.addClass(spClass);
                        var summary = null;
                        $.each(pageEntry.properties, function(key,value){
                           if(key == "dcterms:abstract")
                           {
                               summary = value.replace("<!-- morelink -->", "&nbsp;" + $('<a />').attr('class', 'perc-no-update-link-text perc-blog-list-more-link').attr('href', pageEntry.folder + pageEntry.name).text('...more').appendTo('<div />').parent().html());
                          }
                        });
                        var linkText = pageEntry.linktext;
                        newListElem.find("a").attr("href",pageEntry.folder + pageEntry.name).html(linkText);
                        if(summary)
                            newListElem.find("div").append(summary);
                            listRoot.append(newListElem);
                            currentAutoList.find('.perc-list-main-wrapper').append(listRoot);
                    }
                }
                else
                {
                    //Log the error and leave the original list entries as is
                }
            });
        
        });
    }
})(jQuery);/******************************************************************************
*
 * [ PercResultView.js ]
 * 
 * COPYRIGHT (c) 1999 - 2011 by Percussion Software, Inc., Woburn, MA USA.
 * All rights reserved. This material contains unpublished, copyrighted
 * work including confidential and proprietary information of Percussion.
 *
 * Used to render the pages filtered by a given criteria
 * 
 * Dependecies:
 *  - jquery.js  (1.3.2 or greater)
 *  - json.js 
 *  - simpledateformat.js
  ******************************************************************************/
(function($){
   
   // Publically exposed methods   
   var methods = {
        init: function(options)
            {
                return $(this).each(function()
                    {
                        
                        this.settings = {
                            dateformat: 'EEE MMM d, yyyy \'at\' hh:mm aa', // looks like Tuesday, March 15, 2011 at 11:11 PM
                            dateFormatter: null,
                            summary: null,
                            filter: null,
                            query: null,
                            headerText: 'Results for',
                            readMoreLink:'more...',
                            pagingPagesText: 'pages',
                            pagingOfText: 'of'
                        };
                        
                        if(options)
                            $.extend( this.settings, options );
                        
                        this.settings.dateFormatter = new SimpleDateFormat(this.settings.dateformat);
                        
                        var urlstring = $.deparam.querystring();
                        
                        delete options.summary;
                        
                        if (typeof(urlstring.filter) != "undefined")
                        {
                            this.settings.filter = urlstring.filter;
                        }
                        if(typeof(urlstring.query) != "undefined")
                        {
                            this.settings.query = $.parseJSON(urlstring.query);
                        }
                        if(typeof(options.orderBy) != "undefined" && typeof(this.settings.query) != "undefined" && this.settings.query !== null)
                        {
                            this.settings.query.orderBy = options.orderBy;
                        }
                        
                        // Get maxResults for later use, and set start index for the query.  Index is 0-based post number.
                        if(!(parseInt(this.settings.maxResults, 10) > 0)) // Might already be defined by the paging mechanism, if we're using one.
                        {
                            this.settings.maxResults = parseInt(this.settings.query.maxResults, 10); // Set
                            if (isNaN(this.settings.maxResults) || this.settings.maxResults < 1) // Check
                                this.settings.maxResults = 0; // Reset
                        }
                    });
            },
        /**
         * Queries the server for an updated list of results.
         * @param startIndex The 0 based index of the first entry to ask the server for.
         */
        load: function(startIndex)
            {
                // We don't have a query, so what are we talking to the server for?
                if(!this.settings.query)
                    return false;
                
                if(typeof(startIndex) != "undefined")
                {
                    this.settings.query.startIndex = startIndex;
                }
                else
                {
                    this.settings.query.startIndex = 0;
                }
                
                this.settings.query.maxResults = this.settings.maxResults;
                
                var target = this;
                if (this.settings.query != null){
                    // Return the jqXHR object as per contract.
                    return $.PercResultService.getPageEntries(this.settings.query, function(success, data){
                        target.results = data.results;
                        $(target).PercResultView('updateDisplay', data.totalEntries);
                    });
                }
            },
        updateDisplay: function(respTotalEntries)
            {
                // First, empty the list of all entries.
                el = $(this);
                el.empty();
                
                // Number at the top
                var numEntries = this.totalEntries;
                if (!(numEntries > 0))
                    numEntries = respTotalEntries;
                el.append(createTitleHtml(this.settings, numEntries));
                var titleValue = this.settings.filter;
                setTimeout(function(){updateTitleValue(titleValue)}, 100);
				
                el.append($("<div/>").addClass("perc-result-divider"));
                if(this.results.length > 0)
                {
                    for(c = 0; c < this.results.length; c++)
                    {
                        el.append(createEntryHtml(this.settings, this.results[c]));
                        if(c < (this.results.length - 1))
                        {
                            el.append($("<div/>").addClass("perc-result-divider"));
                        }
                    }
                }
                else
                {
                    // Error while attempting to retrieve data from service
                    // Right now we just fail silently at some point we should try to log
                    // somewhere
                }
            }
    };
    // Define the plugin on the jQuery namespace
    $.fn.PercResultView = function(method)
    {     
        // Method calling logic
        if ( methods[method] ) {
            return methods[ method ].apply( this.get(0), Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            return methods.init.apply( this.get(0), arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.PercResultRenderer' );
        }      
    }
    
    function createTitleHtml(settings, count)
    {
        var title = $("<div/>")
                    .append($("<h2/>")
                            .addClass("perc-result-title")
                            .text(settings.headerText)
                            .append($("<span class='perc-results-title-value'></span>")
                                    .text(settings.filter))
                    ) 
                    .append($("<p/>")
                            .addClass("perc-result-count")
                            .text(count + (count==1?" entry":" entries"))
                    )
        return title;
    }
		
    function updateTitleValue(titleValue)
    {
		try
		{
			var transUrl = "/enes/perc-common-ui/jsp/CreateJsonPair.jsp?paramName=title&paramValue=" + encodeURIComponent(titleValue);
		$.ajax({
		  url: transUrl,
		  success: function(data){
			try
			{
				var titleData = $.parseJSON(data);
				var tagTitle = titleData.title;
				$(".perc-results-title-value").text(tagTitle);
			}
			catch(err)
			{
			}
		  }
		});
		}
		catch(err)
		{
		}
	}
   
    /**
    * Create the page item html from the pageData returned from
    * the server.
    */                               
    function createEntryHtml(settings, entry)
    {
        var pagePath = entry.folder + entry.name;
        var pageItem = $("<div/>").addClass('perc-result-page-item');
        //Page title
        var title = (typeof(entry.properties["dcterms:title"]) == "undefined") ? "" : entry.properties["dcterms:title"];
        pageItem.append($("<h3/>")
                        .addClass("perc-result-page-title")
                        .text(title)
                        .css("cursor", "pointer")
                        .click(function(){window.location = pagePath})
        )
        //Page date
        var datePage = "";
        if ((typeof(entry.properties["dcterms:created"]) != "undefined") && (entry.properties["dcterms:created"] != ""))
        {
            datePage = settings.dateFormatter.format($.timeago.parse(entry.properties["dcterms:created"]));
        }
        pageItem.append($("<p/>")
                        .addClass("perc-result-page-date")
                        .text(datePage)
        )
        // Page summary
        if (settings.summary){
            var pageSummary = (typeof(entry.properties["dcterms:abstract"]) == "undefined") ? "" : entry.properties["dcterms:abstract"];
            pageItem.append($("<div/>")
                            .addClass("perc-result-page-summary")
                            .html(pageSummary.replace('<A xmlns="http://www.w3.org/1999/xhtml" xmlns:perc="http://percussion.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" class="perc-more-link"></A>', "&nbsp;" + $('<a />').attr('class', 'perc-no-update-link-text perc-blog-list-more-link').attr('href', pagePath).text(settings.readMoreLink).appendTo('<div />').parent().html()))
            )
        }
        return pageItem;
    }
    var doLoad = function(target, navLoc, options)
    {
        var startIndex = (target.settings.maxResults*(navLoc - 1));
        if(!(startIndex >= 0))
        {
            startIndex = 0;
        }
        return $(target).PercResultView('load', startIndex);
    };
    /* Auto bind Result instances for existing elements that have
    * a class of <code>perc-result-view</code>.
    */
    $(document).ready(function(){
        $('.perc-result-view').each(function(){
			try
			{
				var $el = $(this);
				var data = $el.attr("data");
				if(typeof(data) == 'string' && data.length > 0)
				{
					var dataObj = $.parseJSON(data);
				}
				else
				{
					dataObj = {};
				}
				$el.PercResultView(dataObj);
				$el.PercResultsPaging(doLoad, {});
			}
			catch(e)
			{
				// Probably a missing or bad query.  Nothing we can do.
			}
        });
    });
   
})(jQuery);/**
 * The delivery side tag list view class. Makes a call to the service to get page list entries and renders them in the
 * list.
 * On document ready loops through all perc-page-tag-list elements on the page and finds the query from the data attribute
 * on them. Passes the query and gets the entries from the service. If service returns an error, logs the error and
 * does nothing.
 */
(function($)
{
    $(document).ready(function(){
        $.PercTagListView.updateTagList();
    });
    $.PercTagListView = {
        updateTagList : updateTagList
    };
    function updateTagList()
    {
        $(".perc-tag-list").each(function(){
            var currentTagList = $(this);
            if (currentTagList.attr("data") === "") return;  // CML-3830 Fixes attempted eval of empty value.
            var queryString = eval("(" + currentTagList.attr("data") + ")");
            //Get the render option: comma separated or list
            var renderOption = queryString.tag_render_option;
            delete queryString.tag_render_option;
            //Get the result page to display the pages for each tag
            var pageResult = queryString.tag_page_result;
            delete queryString.tag_page_result;
            //Get the order by for the list of tags
            var orderBy = queryString.orderBy;
            delete queryString.orderBy;
            //Set the base URL to create the href for each tag then
            var baseURL = window.location.protocol + '//' + window.location.host;
            var strJSON = JSON.stringify(queryString);
            
            $.PercTagListService.getTagEntries(queryString, orderBy, function(status, tagEntries){
                if(status)
                {
                    $('.perc-list-empy-title').remove();
                    //Get the structure and get the root element of the list
                    var listRoot = currentTagList.find(".perc-tag-list-structure .perc-list-main");
                    //Clone the li for future use
                    var listElem = listRoot.find("li").clone();
                    listElem.addClass("perc-tag-element");
                    //Empty the root element and add it to the actual list root.
                    listRoot.empty();
                    currentTagList.find(".perc-list-main-container").empty().remove();
                    currentTagList.find(".perc-comma-separated-main-container").empty().remove();
                    var separator = "";
                    if (typeof(renderOption) != "undefined" && renderOption != "" && renderOption == "commaSeparated")
                    {
                        listRoot.addClass("perc-list-main-inline");
                        listElem.addClass("perc-list-elemment-inline");
                        separator = ',';
                    }
                    else{
                        listRoot.addClass("perc-list-vertical");
                    }
                    currentTagList.append(listRoot);                    
                    //Loop through the tag entries and build the new list element as per the structure.
                    //Then add the newly created element to the list root.
                    for(i=0;i<tagEntries.properties.length;i++)
                    {
                        var tagEntry = tagEntries.properties[i];
                        var newListElem = listElem.clone();
                        var rowClass = i % 2 == 0 ? "perc-list-even":"perc-list-odd";
                        var spClass = "";
                        if (i == 0)
                        {
                            spClass = "perc-list-first";
                        }
                        else if (i==tagEntries.properties.length-1)
                        {
                            spClass = "perc-list-last";
                            separator = "";
                        }
                        
                        newListElem.addClass(rowClass);
                        if(spClass != "")
                            newListElem.addClass(spClass);
                        var linkText = tagEntry.tagName + " (" + tagEntry.tagCount + ")" + separator;
                        //Set the link for the tag
                        if (typeof(pageResult) != "undefined" && pageResult != "")
                        {
                            var query = eval("(" + strJSON + ")");
                            query.criteria.push("perc:tags = '" + tagEntry.tagName + "'");
                            var encodedQuery = "&query=" + encodeURIComponent(JSON.stringify(query));
                            newListElem.find("a").attr("href", baseURL + pageResult + "?filter="+tagEntry.tagName + encodedQuery).html(linkText);
                        }
                        else{
                           newListElem.find("a").html(linkText);
                        }
                        listRoot.append(newListElem);
                    }
                }
                else
                {
                    //Log the error and leave the original list entries as is
                }
            });
        
        });
    }
})(jQuery);

(function ($)
{
	$(document).ready(function(){
		$('.perc-archive-year-link').collapser({
			target: 'siblings',
			effect: 'slide',
			expandClass: 'expArrow',
			collapseClass: 'collArrow',
			changeText: false
		});
	});
})(jQuery);

