/*global CN, console, window, location, document, Option, jQuery, setTimeout, clearTimeout, clearInterval, setInterval */ /* for jsLint */ /* * Conde Nast Digital Core JavaScript * @copyright 2008-2010 Conde Nast Digital except where specified. All rights reserved */ /* * Sets jQuery no conflict; */ jQuery.noConflict(); /* SECTION: EXTENSIONS TO NATIVE OBJECTS */ /* Prototypal inheritance, the missing JavaScript method Author: Andrea Giammarchi Example: newObject = Object.make(oldObject); Reference: http://webreflection.blogspot.com/2008/10/big-douglas-begetobject-revisited.html New version recycles function constructor to cut down on memory consumption and is based on Doug Crockford's original prototypal inheritance function */ if (typeof Object.make !== 'function') { Object.make = function(F) { return function(Object) { F.prototype = Object; return new F(); }; }(function() {}); } /* Memoizes a function - this DOES add to the Function.prototype @author Keith Gaughan @see http://talideon.com/weblog/2005/07/javascript-memoization.cfm */ Function.prototype.memoize = function() { var memo = {}, that = this, obj = arguments.length > 0 ? arguments[i] : null, // TODO: fails jslint, references 'i' out of scope... memoizedFn; memoizedFn = function() { var args = [], i, il; for (i = 0, il = arguments.length; i < il; i++) { args[i] = arguments[i]; } if (!(args in memo)) { memo[args] = that.apply(obj, arguments); } return memo[args]; }; memoizedFn.unmemoize = function() { return that; }; return memoizedFn; }; /* Unmemoizes a function */ Function.prototype.unmemoize = function() { CN.debug.info('Attempted to unmemoize a function that was never memoized in the first place'); return null; }; /* Sugar Arrays (c) Creative Commons 2006 http://creativecommons.org/licenses/by-sa/2.5/ Author: Dustin Diaz | http://www.dustindiaz.com Reference: http://www.dustindiaz.com/basement/sugar-arrays.html */ // IF checks were added by EBS to cover for a bug in jQuery whereby jQuery // adds bogus event bindings to Array.prototype methods. // Ticket is here: http://dev.jquery.com/ticket/6355 // When jQuery fixes this bug, these if (!this.splice) checks can be removed. if (!Array.prototype.forEach) { Array.prototype.forEach = function(fn, thisObj) { if (!this.splice) { return; }; var scope, i, j; scope = thisObj || window; for (i = 0, j = this.length; i < j; ++i) { fn.call(scope, this[i], i, this); } }; Array.prototype.every = function(fn, thisObj) { if (!this.splice) { return; }; var scope, i, j; scope = thisObj || window; for (i = 0, j = this.length; i < j; ++i) { if (!fn.call(scope, this[i], i, this)) { return false; } } return true; }; Array.prototype.some = function(fn, thisObj) { if (!this.splice) { return; }; var scope, i, j; scope = thisObj || window; for (i = 0, j = this.length; i < j; ++i) { if (fn.call(scope, this[i], i, this)) { return true; } } return false; }; Array.prototype.map = function(fn, thisObj) { if (!this.splice) { return; }; var scope, a, i, j; scope = thisObj || window; a = []; for (i = 0, j = this.length; i < j; ++i) { a.push(fn.call(scope, this[i], i, this)); } return a; }; Array.prototype.filter = function(fn, thisObj) { if (!this.splice) { return; }; var scope, a, i, j; scope = thisObj || window; a = []; for (i = 0, j = this.length; i < j; ++i) { if (!fn.call(scope, this[i], i, this)) { continue; } a.push(this[i]); } return a; }; Array.prototype.indexOf = function(el, start) { if (!this.splice) { return; }; var i, j; start = start || 0; for (i = start, j = this.length; i < j; ++i) { if (this[i] === el) { return i; } } return -1; }; Array.prototype.lastIndexOf = function(el, start) { if (!this.splice) { return; }; var i; start = start || this.length; if (start >= this.length) { start = this.length; } if (start < 0) { start = this.length + start; } for (i = start; i >= 0; --i) { if (this[i] === el) { return i; } } return -1; }; } /* * Remove items in an array. Not included in the above because it's not * an official part of the upcoming spec, so its implementation should be * checked separately. * @see http://ejohn.org/blog/javascript-array-remove/ (http://ejohn.org/blog/javascript-array-remove/#comment-296138) */ if (!Array.prototype.remove) { Array.prototype.remove = function(from, to) { if (!this.splice) { return; }; this.splice(from, (to || from || 1) + (from < 0 ? this.length : 0)); return this.length; }; } /* SECTION: CN CORE METHODS */ if (typeof CN === 'undefined' || !CN) { /** * CN global namespace object * @namespace CN global namespace object */ var CN = {}; } /* The following methods are located at the root of CN, because they deal with primitives that jQuery does not identify. No other functions should be placed at this level. */ /** * Determines whether or not the provided object is a boolean * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isBoolean = function(mixed) { return typeof mixed === 'boolean'; }; /** * Determines whether or not the provided object is a date * @param {mixed} mixed The object being tested * @return {boolean} the result */ CN.isDate = function(mixed) { return Object.prototype.toString.call(mixed) === '[object Date]'; }; /** * Determines whether or not the provided string is empty * @param {string} str The string being tested * @return {boolean} the result */ CN.isEmpty = function(str) { return !/\S/.test(str || ''); }; /** * Determines whether or not the provided object is null * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isNull = function(mixed) { return mixed === null; }; /** * Determines whether or not the provided object is a legal number * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isNumber = function(mixed) { return typeof mixed === 'number' && isFinite(mixed); }; /** * Determines whether or not the provided object is of type object * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isObject = function(mixed) { return typeof mixed === 'object'; }; /** * Determines whether or not the provided object is a string * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isString = function(mixed) { return typeof mixed === 'string'; }; /** * Determines whether or not the provided object is undefined * @param {mixed} mixed The object being testing * @return {boolean} the result */ CN.isUndefined = function(mixed) { return typeof mixed === 'undefined'; }; /* SECTION: CN STATIC CLASSES */ /** * @class CN URL Object * @description Contains methods for dealing with urls, query and hash params * @public * @author Paul Bronshteyn * @author Eric Shepherd */ CN.url = (function() { var /** * Path Cache Array. * @memberOf CN.url * @private * @type object */ pathCache = []; /** * @scope CN.url */ return { /** * Retrieves domain name from the url in the form of domain.com * @param {string} [url] Url to be parsed * @return {string} domain.com */ domain : function(url) { var d = ((url) ? url.replace(/^https*:\/\/|(:|\/).*$/g, '') : location.hostname).split('.'), dl = d.length; return d.slice(dl - 2, dl).join('.'); }, /** * Retrieve current site section * @return {string} Section name */ section : function() { return ((location.pathname.split('/')[1] || '').match(/^[^\.]*$/) || [''])[0]; }, /** * Returns url secure state * @return {boolean} */ isSecure: function() { return location.protocol === 'https:'; }, /** * Get query params as object of key, value pairs or a value of a param passed in. If query is not provided, location.search will be used. Result will be caches to queryCache variable for faster access on next call. * @param {string} param Parameter to lookup * @param {string} query Query string to parse * @param {string} regex String key representing regular expression in parsers object * @return {object|string} */ params : function(param, query, regex) { var result = CN.utils.parseStr((query || location.search), (regex || 'query')); return (param) ? result[param] || '' : result; }, /** * Retrive current site path * @return {array} Path */ path : function() { if (pathCache.length === 0) { pathCache = location.pathname.match(/([^\/]+)/g) || ['']; // remove leading and trailing slash. } return pathCache; }, /** * Retrieve the URL fragment identifier * @return {string|boolean} fragment id */ getFragment : function() { return location.hash.substring(1) || false; }, /** * Sets the fragment identifier string */ setFragment : function(value) { location.hash = value || ''; return this; } }; })(); /** * @class CN Utilities * @description Collection of utility helper functions * @public * @author Paul Bronshteyn * @author Eric Shepherd */ CN.utils = (function() { var /** * Cache object. * @description Contains result objects for all parsed string using parseStr function. * @memberOf CN.utils * @private * @type object */ cache = {}, /** * Regular expression parsers * @memberOf CN.utils * @private * @type object */ parsers = { /** * Query, hash parser expression. * @description Will parse a url string in the form of ?var=value&var1=value#hash=value&hash1=value1 into key value pair object. * @memberOf CN.utils * @private * @type RegEx expression */ query : /([^?=&]+)(=([^&]*))?/g, /** * Hash parser expression. * @description Will parse url hash string in the form of ?var=value&var1=value into key value pair object. * @memberOf CN.utils * @private * @type RegEx expression */ hash : /([^#=&]+)(=([^&]*))?/g, /** * User cookie hash parser expression. * @description Will parse a cookie value in the form of var=value|var1=value|var2=value into key value pair object. * @memberOf CN.utils * @private * @type RegEx expression */ usercookie : /([^=|]+)(=([^|]*))?/g }, /** * Takes an argument and a goal length and prepends or appends padding character to reach that length. * @param {string} str A number or string representing a number * @param {integer} total A length to make the return string * @param {string} padding A number or string to pad with * @param {string} dir Direction to pad on * @return {string} Padded string */ pad = function(str, total, padding, dir) { str = String(str || ''); padding = String(padding || ' '); var strLen = str.length, padLen = padding.length; if (strLen >= total) { return str; } while (strLen < total) { str = (dir === 'left') ? padding + str : str + padding; strLen += padLen; } return str; }; /** * @scope CN.utils */ return { /** * Parse string using a regular expression and return object of key, value pairs. * @param {string} query Query to be parsed * @param {string} regex String key representing regular expression in parsers object * @return {object} Result object of key, value pairs */ parseStr : function(str, regex) { if (cache[str+"_"+regex]) { return cache[str+"_"+regex]; } cache[str+"_"+regex] = {}; (str || '').replace(parsers[regex], function($0, $1, $2, $3) { cache[str+"_"+regex][$1] = $3; }); return cache[str+"_"+regex]; }, /** * Intval - Check if variable is an integer * @param {mixed} mixed The scalar value being converted to an integer * @param {integer} [base] The base for the conversion, a number (from 2 to 36) * that represents the numeral system to be used (default is base 10) * @return {integer} Return a number (default is 0) */ intval : function(mixed, base) { if (typeof mixed === 'boolean') { return (mixed) ? 1 : 0; } else if (typeof mixed === 'string') { mixed = parseInt(mixed * 1, (base || 10)); return (isNaN(mixed) || !isFinite(mixed)) ? 0 : mixed; } else if (typeof mixed === 'number' && isFinite(mixed)) { return Math.floor(mixed); } return 0; }, /** * Trim string. * @description Remove leading and trailing space, tab and new lines characters * @param {string} str String to be trimmed * @return {string} Trimmed string * @author Ariel Flesler * @see http://flesler.blogspot.com/2008/11/fast-trim-function-for-javascript.html */ trim : function(str) { var start = -1, end = str.length; while (str.charCodeAt(--end) < 33); // TODO: fails jslint - no while block while (++start < end && str.charCodeAt(start) < 33); // TODO: fails jslint - no while block return str.slice(start, end + 1); }, /** * Transliterate string. * @description converts extended ascii characters to normal versions * @param {string} str String to be transliterated * @param {boolean} strip Whether or not to delete unknown characters. default: true * @return {string} string * @author Daniel Holly Wells */ transliterate : function(str, strip) { if (typeof str == "undefined") { return ""; } if (typeof strip == "undefined") { strip = true; } str = escape(str) .replace(/%C[0-5]/g ,'A') .replace(/%C6/g ,'AE') .replace(/%C7/g ,'C') .replace(/%C[8-9|A-B]/g ,'E') .replace(/%C[C-F]/g ,'I') .replace(/%D[2-8]/g ,'O') .replace(/%D[9|A-C]/g ,'U') .replace(/%DD/g ,'Y') .replace(/%u0178/g ,'Y') .replace(/%u017D/g ,'Z') .replace(/%u0160/g ,'S') .replace(/%E[0-5]/g ,'a') .replace(/%E6/g ,'ae') .replace(/%E7/g ,'c') .replace(/%E[8-9|A-B]/g ,'e') .replace(/%E[C-F]/g ,'i') .replace(/%F[2-8]/g ,'o') .replace(/%F[9|A-C]/g ,'u') .replace(/%F[D-F]/g ,'y') .replace(/%u017E/g ,'z') .replace(/%u0161/g ,'s') .replace(/%u2014/g ,'-') .replace(/%u2013/g ,'-') .replace(/%u201[8-9]/g ,"'") .replace(/%u201A/g ,',') .replace(/%u2026/g ,'...') .replace(/%u201[C-D]/g ,'"') .replace(/%3F/g ,'?') .replace(/%21/g ,'!') .replace(/%26/g ,'&') .replace(/%25/g ,'%') .replace(/%24/g ,'$') .replace(/%5E/g ,'^') .replace(/%28/g ,'(') .replace(/%29/g ,')') .replace(/%7E/g ,'~') .replace(/%60/g ,'`') .replace(/%23/g ,'#') .replace(/%3D/g ,'=') .replace(/%2C/g ,',') .replace(/%3C/g ,'<') .replace(/%2E/g ,'>') .replace(/%7C/g ,'|') .replace(/%3A/g ,':') .replace(/%3B/g ,';') .replace(/%7D/g ,'}') .replace(/%7B/g ,'{') .replace(/%5B/g ,'[') .replace(/%5D/g ,']') .replace(/%20/g ,' '); if (strip) { str = str.replace(/%u[0-9|A-F][0-9|A-F][0-9|A-F][0-9|A-F]/g, '').replace(/%u[0-9|A-F][0-9|A-F]/g, '').replace(/%[0-9|A-F][0-9|A-F]/g, ''); } else { str = unescape(str); } return str; }, /** * Takes an argument and a goal length and prepends padding character to reach that length. * @param {mixed} str A number or string representing a number * @param {integer} total A length to make the return string * @param {mixed} padding A number or string to pad with * @return {string} Padded string * @uses CN.utils.pad */ padLeft : function(str, total, padding) { return pad(str, total, padding, 'left'); }, /** * Takes an argument and a goal length and appends padding character to reach that length. * @param {mixed} str A number or string representing a number * @param {integer} total A length to make the return string * @param {mixed} padding A number or string to pad with * @return {string} Padded string * @uses CN.utils.pad */ padRight : function(str, total, padding) { return pad(str, total, padding, 'right'); }, /** * URI encode/decode a string * @private * @param {string} str String to encoded or decoded * @param {boolean} [encode] Will encode if set to true, otherwise decode * @return {string} Encoded or decoded string */ uriencdec : function(str, encode) { return (encode) ? encodeURIComponent(str) : decodeURIComponent(str); }, /** * Converts a property array to an object of values * * @param {array|object} arr An array of key/value objects (or object as fallback) * @param {string} name The name to use (defaults to 'name') * @param {string} value The value to use (defaults to 'value') * @return {object} The resulting mapped object * @example mapPropertyArray([{'name':'left','value':200},{'name':'top','value':300}]); * returns this object: * { 'top' : 300, 'left' : 200 } */ mapPropertyArray : function(arr, name, value) { name = name || 'name'; value = value || 'value'; var obj = {}; if (jQuery.isArray(arr)) { jQuery.each(arr, function(i) { obj[arr[i][name]] = arr[i][value]; }); } else { obj[arr[name]] = arr[value]; } return obj; } }; })(); /** * CN Debug Object * @requires jQuery * @class CN Debug Object * @public * @constructor * @author Paul Bronshteyn */ CN.debug = (function() { var /** * Log Types (error, warn, info, user) * @memberOf CN.debug * @private * @type object */ eType = { error : { f: 'error', msg: 'ERROR' }, warn : { f: 'warn', msg: 'WARNING' }, info : { f: 'info', msg: 'INFO' }, user : { f: 'error', msg: 'USER' } }, /** * Log Types (DEV, STAG, PREV, PROD) * @memberOf CN.debug * @private * @type object */ eEnv = { DEV : 'Development', STAG : 'Staging', PREV : 'Preview', PROD : 'Production' }, /** * Shows error information in console or alert * @memberOf CN.debug * @private * @param {string} type Error Type * @param {string} msg Error message * @param {array} [args] Error details */ show = function(type, msg, args) { var t = eType[type] || eType.debug; if (CN.site.env === 'PROD' && !CN.site.debug) { return; } msg = msg || 'NO MSG'; args = args || []; if (typeof console === 'object') { var func = console[t.f] || console.info; args.unshift(t.msg, msg); for (var i = 0; i < args.length - 1; i += 2) { var part = args.splice(0, i + 1); part.push(' :: '); args = part.concat(args); } if (console.firebug) { func.apply(this, args); } else { console[t.f](args); } } }; if (CN.url.params("debugOff") === 'true') { show = function() { return; }; } /** * @scope CN.debug */ return { /** * Log error messages * @param {string} msg Error message * @param {array} [args] Error details */ error : function(msg, args) { show('error', msg, args); return this; }, /** * Log warning messages * @param {string} msg Warning message * @param {array} [args] Warning details */ warn : function(msg, args) { show('warn', msg, args); return this; }, /** * Log info messages * @param {string} msg Info message * @param {array} [args] Info details */ info : function(msg, args) { show('info', msg, args); return this; }, /** * Log Try/Catch messages * @param {object} e Error object * @param {array} [args] Error details */ user : function(e, args) { show('user', e.message, [args, e.fileName, e.lineNumber, e.name, e.stack]); return this; }, /** * Speed test your function * @param {function|string} f Function name or it's string representation * @param {array} [args] Arguments that will be passed to the function * @param {integer} [cycles] How many cycles to run the test (default 10000) * @return {console|alert} Prints time in ms in console in FF,Safari,Chrome and alert() on IE */ speedtest : function(f, args, cycles) { var x, i; if (CN.isNumber(args)) { cycles = args; args = []; } if (!jQuery.isArray(args)) { args = []; } cycles = cycles || 10000; if (!jQuery.isFunction(f)) { CN.debug.error('Not a function', [f]); return this; } if (typeof console === 'object') { if (console.time) { x = 'timer' + Math.floor(Math.random() * 1000000); console.time(x); for (i = 0; i < cycles; i++) { f.apply(this, args); } console.timeEnd(x); } else { x = new Date() - 0; for (i = 0; i < cycles; i++) { f.apply(this, args); } x = new Date() - x; console.log(x); } } else { x = new Date() - 0; for (i = 0; i < cycles; i++) { f.apply(this, args); } x = new Date() - x; alert(x); } return this; }, /** * CN Application Debug Object * @class CN Application Debug Object * @constructor * @public * @author Paul Bronshteyn */ app : function() { var /** * Holds setLevel options * @memberOf CN.debug.app * @private * @type object */ options = {}, /** * Shows error information in console or alert. * @description Uses setLevel options to display or supress error messages. Calls parent show() method if setLevel options match * @memberOf CN.debug.app * @link CN.debug.show * @private * @param {string} type Error Type * @param {string} msg Error message * @param {array} [args] Error details */ _show = function(type, msg, args) { if (options[CN.site.env][type]) { show(type, msg, args); } }; /** * @scope CN.debug.app */ return { /** * Set Levels of debuging messages * @param {array} type Log Types (error, warn, info, debug, user) * @param {string} [env] Enviroment (DEV, STAG, PREV, PROD) */ setLevel : function(type, env) { if (!type || !jQuery.isArray(type) || type.length === 0) { return this; } env = (env && env in eEnv) ? env : 'DEV'; options[env] = type; return this; }, /** * Get Levels of debuging messages * @param {string} [env] Enviroment (DEV, STAG, PREV, PROD) * @return {object|array} If enviroment not provided returns reporting object, if provided levels array */ getLevel : function(env) { return (env) ? options[env] || '' : options; }, /** * Log error messages * @link CN.debug.error * @param {string} msg Error message * @param {array} [args] Error details */ error : function(msg, args) { _show('error', msg, args); return this; }, /** * Log warning messages * @link CN.debug.warn * @param {string} msg Warning message * @param {array} [args] Warning details */ warn : function(msg, args) { _show('warn', msg, args); return this; }, /** * Log info messages * @link CN.debug.info * @param {string} msg Info message * @param {array} [args] Info details */ info : function(msg, args) { _show('info', msg, args); return this; }, /** * Log Try/Catch messages * @link CN.debug.user * @param {object} e Error object * @param {array} [args] Error details */ user : function(e, args) { _show('user', e.message, [args, e.fileName, e.lineNumber, e.name, e.stack]); return this; } }; } }; })(); /** * Intercept window errors, log them quietly. * @description The error will be intercepted on all enviroments and suppresed on production enviroment (this should be optional). * @name onerror * @event * @param {string} msg Error message * @param {string} url URL of the error * @param {integer} line Line number * @return {boolean} */ if (CN.url.params("debugOff") !== 'true') { window.onerror = function(msg, url, line) { CN.debug.error(msg, [url, line]); return (CN.site.env === 'PROD') ? true : false; }; } /** * CN Site Object * @class CN Site Object * @public * @author Paul Bronshteyn */ CN.site = (function() { /** @scope CN.site */ return { /** * Site code * @type string */ code : '', /** * Site title * @type string */ title : '', /** * Site name - Lower cased title * @type string */ name : '', /** * Site alias - Upper case title * @type string */ alias: '', /** * Site environment * @type string */ env : '', /** * Site CND Request * @type boolean */ cnd: false, /** * Site debug. * @description If set will console debug messages in any enviroment. Use query parameter magdebug to toggle debuger. * @type boolean */ debug : !!CN.url.params('magdebug') && !this.cnd, /** * Site no ads. * @description If set will disable ad calls on the page. * @type boolean */ noads : !!CN.url.params('magnoads') && !this.cnd, /** * Test ads. * @description If not empty we will use this as dart site and zone * @type String */ testads : CN.url.params('dartAdOverride') && !this.cnd, /** * Initiate site specific object, sets document.domain * @param {object} settings S * @type function */ init : function(settings) { settings = settings || {}; for (var s in settings) { if (settings.hasOwnProperty(s)) { this[s] = settings[s]; } } /** * @name CN.site#dynamicName * @description Dynamically generated site object based on the name of the site. All site specific code will be in this object. * @memberOf CN.site * @type object * @example CN.site.glamour */ this[this.name] = {}; this.domain = CN.url.domain(); try { if (this.domain) { document.domain = this.domain; } CN.debug.info('Document domain was set', [this.domain]); } catch(e) { CN.debug.error(e); } CN.debug.info('CN Started', [this.code, this.title, this.env, this.name, this.alias, this.cnd, this.debug, this.noads]); return this; } }; })(); /********************************************************************************************************************* The above namespaces need to be in the order listed All namespaces below will follow in alphabetical order *********************************************************************************************************************/ /** * @class CN callwhen * Method for evaluating call[before || back] events. * @description Ensures, parses, and triggers all call[before || back] events pass * in object fincs. * @public * @author Russell Munson * @example funcs={ callbefore : [{ func: function(){ dosomething();}, //Function to be executed params: ["string",{object}], //Params to be passed to function scope : CN.dart //Scope to execute function in }], callbefore : [{ func: function(){ dosomething();}, params: ["string",{object}], scope : CN.dart }] } */ CN.callwhen = { /* Add function to the callbefore/callback queue * * @param {object} queue State to queue "before||after". * @param {object} func) Object containing the function, params, and scopes. * ex: { func: function(e,str){return str;}, * params:["function running"], * scope : window} * @param {object} [queue] CN.callwhen object to add function to, or {}. */ add : function(state,funcO,queue){ if(!state || !funcO || !funcO["func"]) {return;} queue=queue || {}, state = (state==="after" ? "callback" : "callbefore"), funcO= { func : funcO["func"], params : funcO["params"] || [], scope : funcO["scope"] || window }; if(jQuery.isFunction(funcO.func) && CN.isString(state)){ queue[state] = queue[state] || [], queue[state].push(funcO); } return queue; }, /* Execute the functions in all defined states function * @param {object} funcs Object containing callwhen events * @param {dom el} [target] Optional DOM element to bind event to, which defaults to window. * @param {string} [state] Optional state to execute "before||after||both". Defaults is both. */ run : function(funcs,target,state){ if (!CN.isObject(funcs)){ return; } if (CN.isString(target)){ state=target, target=window; } var funcThis, funcObj; /* Run through the callbefore queue */ if ((state==="before" || !state) && jQuery.isArray(funcs.callbefore)){ for (var i=0,len=funcs.callbefore.length; i 3 ? 'long' : 'short'; return CN.date.getDayName(d.getDay(), opt); }, // a - AM/PM marker a : function(d, number) { return d.getHours() < 12 ? 'AM' : 'PM'; }, // H - Hour in day (0-23) H : function(d, number) { return zeroPad(d.getHours(), number); }, // k - Hour in day (1-24) k : function(d, number) { return zeroPad(d.getHours() + 1, number); }, // K - Hour in am/pm (0-11) K : function(d, number) { var hours = d.getHours(); return zeroPad(hours - 12 >= 0 ? hours - 12 : hours, number); }, // h - Hour in am/pm (1-12) h : function(d, number) { var hours = d.getHours(); return zeroPad((hours - 13 >= 0 ? hours - 12 : hours), number); }, // m - Minute in hour m : function(d, number) { return zeroPad(d.getMinutes(), number); }, // s - Second in minute s : function(d, number) { return zeroPad(d.getSeconds(), number); }, // S - Millisecond S : function(d, number) { return zeroPad(d.getMilliseconds(), number); } // z - Time zone (general) // Not Supported // Z - Time Zone (RFC 822 e.g. -0800) // Not Supported }; /** * @scope CN.date */ return { /** * Determines whether or not the provided year is a leap year * @param {number} year The year being tested * @return {boolean} Whether or not the year is a leap year */ isLeapYear : function(year) { return !!(year && (year % 4 === 0) && (year % 100 !== 0 || year % 400 === 0)); }, /** * Get the number of days in the given month * @param {number} month Month number (0-11) where January is 0 * @param {number} year The year * @return {number} The number of days in the month */ getDaysInMonth : function(month, year) { return (month === 1 && this.isLeapYear(year)) ? 29 : [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] || 0; }, /** * Get month name. * @description Returns month name for specified month index and language, the language will default to English if not provided. * @param {integer} month Month number (0-11) where January is 0, February is 1 and so on * @param {object} options Language, short or long form * @option {string} lang ISO 639-1 language code (default "en") * @option {string} form Type of form to use (default "long") * @return {string} Month name or Empty * @link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes */ getMonthName : function(month, options) { options = options || {}; return CN.date.getMonthNames(options)[month] || ''; }, /** * Get month names array. * @description Returns array of month names for specified language * @param {string} lang ISO 639-1 language code * @return {array} Month names or Empty * @link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes */ getMonthNames : function(options) { options = options || {}; return months[options.lang || 'en']['_' + (options.form || 'long')] || []; }, /** * Get day name. * @description Returns day name for specified month index and language; the langauge will default to English if not provided. * @param {number} day Day number (0-6) where Sunday is 0 * @param {object} options Language, short or long form * @option {string} lang ISO 639-1 language code (default "en") * @option {string} form Type of form to use (default "long") * @return {string} Day name or empty * @link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes */ getDayName : function(day, options) { options = options || {}; return CN.date.getDayNames(options)[((day === 7) ? 0 : day)] || ''; }, /** * Get day of the week names array. * @description Returns array of day of week names for specified language * @param {object} [options] Language and name options * @option {string} lang ISO 639-1 language code (default "en") * @option {string} Type of form to use (default "long") * @return {array} Month names or Empty array * @link http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes */ getDayNames : function(options){ options = options || {}; return days[options.lang || 'en']['_' + (options.form || 'long')] || []; }, /** * Format a JavaScript date to a readable format * For now, this is only a format method. It can be expanded to parse a string into a date later. * @description Returns a formatted date * @param {object} d A JavaScript date object * @param {string} pattern A formatting string, per Java's SimpleDateFormat specification * @return {string} A formatted date string * @link http://java.sun.com/j2se/1.4.2/docs/api/java/text/SimpleDateFormat.html */ format : function(d, pattern) { // For now, only accepting date objects. if (!CN.isDate(d)) { CN.debug.warn('date.format() method requires a JavaScript date object to be passed'); return d; } var str = '', p = function(q) { // Utility function to push into the string we will be returning str += q; }, i, il, current = '', flagLength = 1, currentToEnd, subPattern; /// END VAR BLOCK if (CN.isString(pattern)) { pattern = pattern.split(''); // pattern is now an array since IE can't do String[n] for (i = 0, il = pattern.length; i < il; i++) { current = pattern[i]; // If there's a flag, call it, else parse literally while accounting for single quotes if (formatFlags[current]) { // Keeps increasing flagLength if letters are repeated and match a flag while (pattern[i + flagLength] === pattern[i]) { flagLength += 1; } // Calls the method for the given flag p(formatFlags[current](d, flagLength)); } else { // If we get a single quote if (pattern[i] === '\'') { if (pattern[i + 1] !== '\'') { currentToEnd = pattern.slice(i + 1, pattern.length); subPattern = currentToEnd.slice(0, currentToEnd.indexOf("'")); p(subPattern.join('')); flagLength += (subPattern.length + 1); } else { p("'"); flagLength += 1; } } else { p(pattern.slice(i, i + 1).join('')); } } i += (flagLength - 1); // jump i ahead by the number of letters current = ''; // reset flagLength = 1; // reset } } else { // Fallback to generic toString() representation if no format was passed in str = d.toString(); } return str; }, /** * Converts an ISO-8601 date (W3C subset) into a JavaScript date * http://www.w3.org/TR/NOTE-datetime * * @param {String} d A date string in ISO-8601 format * @return {Date} A JavaScript Date object */ isoToDate : function(isoDate) { var regex = /([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?/, // TODO: fails jslint - unescaped - at 116 offset = 0, date = isoDate.match(regex), d = new Date(date[1], 0, 1); if (date[3]) { d.setMonth(date[3] - 1); } if (date[5]) { d.setDate(date[5]); } if (date[7]) { d.setHours(date[7]); } if (date[8]) { d.setMinutes(date[8]); } if (date[10]) { d.setSeconds(date[10]); } if (date[12]) { d.setMilliseconds(Number('0.' + date[12]) * 1000); } if (date[14]) { offset = (Number(date[16]) * 60) + Number(date[17]); offset = offset * ((date[15] == '-') ? 1 : -1); } offset = offset - d.getTimezoneOffset(); d.setTime(Number(Number(d) + (offset * 60 * 1000))); return d; }, /** * Converts a Teamsite date into the standard ISO-8601 format * Using timezone offset that should at least give us the right day when the teamsite time is set to midnight. * * @param {String} iwDate A date in InterWoven Teamsite format (20081204 11:23:34) * @return {String} A date in ISO-8601 format */ convertIwDateToIso : function(iwDate) { var isoDate = ''; if (CN.isString(iwDate)) { isoDate = iwDate.substr(0, 4) + '-' + iwDate.substr(4,2) + '-' + iwDate.substr(6, 2) + 'T' + iwDate.substr(9, 8) + '-05:00'; } return isoDate || ''; } }; })(); /** * @class CN Frame * @description Contains methods for dealing with iFrames. * @public * @requires jQuery * @author Paul Bronshteyn */ CN.frame = (function($) { var /** * Resize iFrame height to fit content on load. * @description This is a private function that is triggered by the onload event of the iFrame. This will also be triggered by the public resize method. * @memberOf CN.frame * @private * @event */ _resize = function(e) { var frame = (e.data && e.data.use) ? e.data.use : this; try { var body = frame.contentWindow.document.body; } catch(e) { return CN.debug.user(e, [frame, frame.id]); } if (typeof e.data === 'undefined') { $('iframe', body).bind('load', { use: frame }, _resize); } if (!$('.textAd', body).length || !$('#adHolder a', body).eq(0).text()) { $('#adHolder', body).css({ 'font-size': 0, 'line-height': 0 }); } $(frame).css({ border : 'none', margin : 0, height : $(body).css({ overflow: 'hidden', margin: 0, border: 0 }).outerHeight() }); }; /** * @scope CN.frame */ return { /** * Bind iFframe resize on iFrame load. * @description Binds the load event to the element passed in. * @param {string} frame ID or class of the iFrame in jQuery excepted format. * @uses CN.frame._resize * * @example Using element id: CN.frame.bindResize('#frame_id'); Using element class: CN.frame.bindResize('.frame_class'); Using multiple and combinations: CN.frame.bindResize('#frame_id, .frame_class'); */ bindResize : function(frame) { $(frame).bind('load', _resize); return this; }, /** * Refresh iFrame * @description Refreshes an iFrame with the current url or with the url if the param (if provided), resizes the frame onload to fit content. * @param {string,array} frames Array, CSV or space-delimitted list of iframe classes or ids or mixed * @param {string} [url] Url for the iFrame(s) to be refreshed with, defaults to refreshing current iFrame url * @param {boolean} [resize] Resize iFrame after refresh, default is true * @uses CN.frame._resize * * @example Refresh iFrame: CN.frame.refresh('#frame_id'); Refresh multiple iFrames (comma-separated): CN.frame.refresh('#frame_id,.frame1,#frame2'); Refresh multiple iFrames (space-separated): CN.frame.refresh('#frame_id .frame1 #frame2'); Refresh iFrame with url: CN.frame.refresh('#frame_id', 'http://web.archive.org/web/20120515135258/http://www.example.com'); Refresh iFrame with url and do not resize: CN.frame.refresh('#frame_id', 'http://web.archive.org/web/20120515135258/http://www.example.com', false); */ refresh : function(frames, url, resize, funcs) { frames = (CN.isString(frames)) ? frames.split(/,|\s+/) : ($.isArray(frames)) ? frames : []; // frames array empty? exit if (!frames.length) { return this; } // shift arguments if resize was ommited if(CN.isObject(resize)){ funcs=resize,resize=true; } // shift arguments if url and resize were ommited if(CN.isObject(url)){ funcs=url,url=''; } // shift arguments if url was ommited if (CN.isBoolean(url)) { resize = url; url = ''; } // NOTE: == is intentional and checks for values that are null or undefined resize = (resize == null) ? true : resize; // update each frame $.each(frames, function(i, v) { if (!/\S/.test(v)) { return true; } var frame = $(v); if (!frame.length) { return true; } // bind load event if (resize) { frame.bind('load', _resize); } //execute callbefore, and bind callafter CN.callwhen.run(funcs,frame); url = url || frame[0].src; // load url provided or refresh // adblock extension throws error, catch it, kill it try { frame[0].contentWindow.location.replace(url); CN.debug.info('CN Frame Refresh', [v, url, resize]); } catch(e) { CN.debug.user(e, [v, url, resize]); } }); return this; }, /** * Resize iFrame height to fit content. * @description Binds the load event to the element passed in and then triggers it. * @param {string} frame ID or class of the iFrame in jQuery excepted format. * @uses CN.frame._resize * * @example Using element id: CN.frame.resize('#frame_id'); Using element class: CN.frame.resize('.frame_class'); Using multiple and combinations: CN.frame.resize('#frame_id, .frame_class'); */ resize : function(frame) { $(frame).bind('load', _resize).triggerHandler('load'); return this; } }; })(jQuery); /** * @class CN Internal Object * @description Handles functionality for non-production environments. * @public * @author Eric Shepherd */ CN.internal = (function() { return { getTeamsiteServer : function() { return 'deprecated'; } }; })(); /** * @class CN.media * @description Methods to handle various types of media * @public * @author Eric Shepherd */ CN.media = (function($) { var embedSwf, embedSwfWithTimeout; /** * Makes a call to swfobject to embed a swf movie with the given * parameters. * * @method embedSwf * @private * @param args {Array} An array of params for the swfobject call */ embedSwf = function(args) { swfobject.embedSWF.apply(window, args) }; /** * Sets a timer to allow dynamically loaded swfobject.js to be parsed * before running code dependent on it. * * @method embedSwfWithTimeout * @private * @param args {Array} An array of params for the swfobject call */ embedSwfWithTimeout = function(args) { setTimeout(function() { embedSwf(args) }, 200); }; /** * @scope CN.media */ return { /** * Embeds a Flash movie using swfobject.js, loading it if it's not * already included on the page. * * @public * @method swf * @param args {Array} An array of params for the swfobject call */ swf : function(args) { if (typeof swfobject === 'undefined') { CN.debug.info('Dynamically loading swfobject.js - consider placing the script call in the site JSP if load time/FOUC is an issue.'); $.getScript('/js/cn-fe-common/misc/swfobject.js', function() { embedSwfWithTimeout(args); }); } else { CN.debug.info('swfobject.js already loaded or being loaded on page, using it to render mediaItem'); embedSwf(args); } } }; })(jQuery); /** * @class CN.modules * @description Holds page module APIs and provides methods for interacting with modules * @public * @author Eric Shepherd */ CN.modules = (function() { CN.customEvents = CN.customEvents || {}; CN.customEvents.moduleLoaded = {}; var register, loaded = []; register = function(moduleName) { CN.modules.loaded.push(moduleName); jQuery(window).trigger('CN.customEvents.moduleLoaded.' + moduleName); }; return { register : register, loaded : loaded } })(); /** * @class CN.page * @description Page level information * @public * @author Paul Bronshteyn * @author Eric Shepherd */ CN.page = (function() { return { config : {}, /** * The section of the site we are in * @memberOf CN.page * @public * @return {String} The current site section, or empty */ section : function() { return ((location.pathname.split('/')[1] || '').match(/^[^\.]*$/) || [''])[0]; }, /** * The subsection of the site we are in, if applicable * @memberOf CN.page * @public * @return {String} The subsection of the site, or empty */ subsection : function() { return ((location.pathname.split('/')[2] || '').match(/^[^\.|(\d{4})]*$/) || [''])[0]; }, /** * The content slug of the current page, if applicable * @memberOf CN.page * @public * @return {String} The current page's slug, or empty */ slug : function() { return ((location.pathname.split('/')[location.pathname.split('/').length-1] || '').match(/^[^\.]*$/) || [''])[0]; } }; })(); /** * @description Reg specific methods. If your function is used on * registration, and it doesn't fit anywhere else... then it belongs here. * @class CN Reg * @public * @author Paul Bronshteyn * @author Russell Munson */ CN.reg = (function($) { var form = {}, reqClass = "rqrd"; function formBindings() { form.bind('submit', function() { var bdayfield = $('#bdayfield', this); if (bdayfield.length && $('#birthYear', this).val() != 'YYYY') { bdayfield.val([$('#birthMonth', this).val(), $('#birthDay', this).val(), $('#birthYear', this).val()].join('/')); } }); } return { /** * Set the form context for the usage in CN.reg * * @public * @param {String} fid Takes jQuery formatted selector pointing to form * @returns {Object} Returns CN.reg for easy command chaining. */ setForm : function(fid){ form = $(fid); formBindings(); return this; }, /** * Return for jQuery object containing the form currently in context * * @public * @returns {Object} jQuery object conaining form currently in context */ getForm : function(){ return form; }, /** * Concatenates values of separate birthday fields with a '/' deliminator * * @public */ setBirthday : function() { var bdayfield = $('#bdayfield', form); if (bdayfield.length) { var fields = bdayfield.val().split('/'); $('#birthMonth', form).val(fields[0]); $('#birthDay', form).val(fields[1]); $('#birthYear', form).val(fields[2]); } }, /** * Add css class indicator for required fields. Assumes * standard regForm markup, with parent .row containing class defined in reqClass * * @public */ setReq : function(el) { $(el).parents('.row').addClass(reqClass); }, /** * Remove css class indicator for required fields. Assumes * standard regForm markup, with parent .row containing class defined in reqClass * * @public */ removeReq : function(el) { $(el).parents('.row').removeClass(reqClass); }, /** * Return class name for marking required fields in a reg form * * @public */ getReqClass : function() { return reqClass; } }; })(jQuery); /** * @class CN Search * @public * @author Paul Bronshteyn */ CN.search = (function() { var /** * RegEx checks to validate search string * @memberOf CN.search * @private */ checks = { alphanum : /[^0-9a-zA-Z\s]/g, script : //g }; /** * @scope CN.search */ return { /** * Generate search path. * @description Sanitizes the string first, replaces all spaces with - * @param {string} keywords Search keywords * @return {string} Sanitized search path * @uses CN.search.sanitize */ path : function(keywords) { return this.sanitize(keywords).replace(/\s+/g, '-'); }, /** * Sanitize query string. * @description Remove <script/> tags to prevent XSS and all non-alpha numeric characters. * @param {string} keywords Search keywords * @return {string} Sanitized keywords * @uses CN.utils.trim */ sanitize : function(keywords) { return CN.utils.trim(keywords || '').replace(checks.script, '').replace(checks.alphanum, ''); } }; })(); /** * @class CN User * @public * @author Paul Bronshteyn */ CN.user = (function() { /** * @scope CN.user */ return { /** * Determine if the user is logged in? * @return {boolean} * @uses CN.cookie.get */ isLoggedIn : function() { return CN.cookie.get('amg_user_record') && CN.cookie.get('amg_user'); }, /** * Determine if user has confirmed their registration * @return {boolean} * @uses CN.utils.parseStr * @uses CN.cookie.get */ isConfirmed : function() { return CN.utils.parseStr(CN.cookie.get('amg_user_record'), 'usercookie').conf === 'true'; }, /** * Get logged in username * @return {string} Username * @uses CN.utils.parseStr * @uses CN.cookie.get */ username : function() { return CN.utils.parseStr(CN.cookie.get('amg_user_record'), 'usercookie').username || ''; }, /** * Get user id * @return {string} id * @uses CN.utils.parseStr * @uses CN.cookie.get */ userid : function() { return CN.utils.parseStr(CN.cookie.get('amg_user_record'), 'usercookie').uid || 0; } }; })(); /** * @description Methods invloving geo-location, states, provinces, countries... anything * world oriented * @class CN World * @public * @author Paul Bronshteyn * @author Russell Munson */ CN.world = (function($) { var states = { msg : 'Select your', us : { desc: 'state', code: 'AL,AK,AZ,AR,CA,CO,CT,DE,DC,FL,GA,HI,ID,IL,IN,IA,KS,KY,LA,ME,MD,MA,MI,MN,MS,MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,OH,OK,OR,PA,RI,SC,SD,TN,TX,UT,VT,VA,WA,WV,WI,WY,AE,AA,AP'.split(','), name: 'Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District of Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New Hampshire,New Jersey,New Mexico,New York,North Carolina,North Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode Island,South Carolina,South Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West Virginia,Wisconsin,Wyoming,Armed Forces Europe,Armed Forces Americas,Armed Forces Pacific'.split(',') }, ca : { desc: 'province', code: 'AB,BC,MB,NB,NL,NT,NS,NU,ON,PE,QC,SK,YT'.split(','), name: 'Alberta,British Columbia,Manitoba,New Brunswick,Newfoundland and Labrador,Northwest Territories,Nova Scotia,Nunavuta,Ontario,Prince Edward Island,Quebec,Saskatchewan,Yukon'.split(',') } }, /** * Defaults the form context for the usage in CN.world to the form currently * in context for CN.reg. * * @protected * @returns {Object} Returns jQuery object containing a form * @see CN.reg.getForm */ form = function() { return CN.reg.getForm(); }; return { /** * Set the form context for the usage in CN.reg * * @public * @param {String} fid Takes jQuery formatted selector pointing to form * @returns {Object} Returns CN.reg for easy command chaining. */ setForm : function(fid) { form = $(fid); return this; }, /** * Event that handles coordination between the field containing the currently * selected country value, and the field #state which lists the states/provinces * currently supported by CN Digital reg forms. State field is disabled for non-supported * nations * * @type Event * @public */ setState : function() { var stateField = $('#state', form), zipField = $("#zip", form), selection = this.value.toLowerCase(); if (!(selection in states)) { stateField.attr({disabled: 'true'}); zipField.attr({disabled: 'true'}).data("val",zipField[0].value).val(""); stateField[0][0].selected = true; CN.reg.removeReq("#zip, #state"); } else { CN.reg.setReq("#zip, #state"); zipField[0].value = (zipField.attr({disabled: ''}).data("val") || zipField[0].value); var choice = stateField.children("[selected]").val() || false; stateField.empty(); stateField.attr('disabled', '')[0][0] = new Option(states.msg + ' ' + states[selection].desc, ''); $.each(states[selection].code, function(i) { stateField[0][i + 1] = new Option(states[selection].name[i], this); if (choice && choice == this) { // TODO: is this == intentional? stateField[0][i + 1].selected = true; } }); } } }; })(jQuery); /* SECTION: CN INSTANTIABLE CLASSES */ /** * Interface creation and verification methods * @class Interface * @constructor * @author Ross Harmes and Dustin Diaz, from Pro JavaScript Design Patterns * * @param name {String} The name of the interface. Preferable to use IName convention. * @param methods {Array} The methods which need to be implemented by the child classes. */ CN.Interface = function(name, methods) { var i, il; if (arguments.length !== 2) { throw new Error('CN.Interface constructor called with ' + arguments.length + ' arguments, but expected exactly 2'); } this.name = name; this.methods = []; for (i = 0, il = methods.length; i < il; i++) { if (typeof methods[i] !== 'string') { throw new Error('CN.Interface constructor expects method names to be passed in as strings'); } this.methods.push(methods[i]); } }; /** * Verifies that a class implements a given interface. * @method ensureImplements * @static * * @param object {Object} Any object to verify */ CN.Interface.ensureImplements = function(object) { var i, il, inter, j, jl, method; if (arguments.length < 2) { throw new Error('Static method CN.Interface.ensureImplements called with ' + arguments.length + ' arguments, but expected at least 2'); } for (i = 1, il = arguments.length; i < il; i++) { inter = arguments[i]; if (inter.constructor !== CN.Interface) { if (jQuery.browser.safari && jQuery.browser.version < 500) { // do nothing - safari 2 can't handle this constructor check, this is a patch fix for now } else { throw new Error('Static method CN.Interface.ensureImplements expects arguments two and above to be instances of CN.Interface.'); } } for (j = 1, jl = inter.methods.length; j < jl; j++) { method = inter.methods[j]; if (!object[method] || typeof object[method] !== 'function') { throw new Error('Static method CN.Interface.ensureImplements: object does not implement the ' + inter.name + ' interface. Method ' + method + ' was not found.'); } } } }; /** * Creates an observable object * @class Observer * @constructor */ CN.Observer = function(label) { /** * @property * @static */ this.fns = []; /** * @property * @static */ this.label = label || null; }; /** * Holds the list of fired observers which provided a label at contruction time * @property * @static * @memberOf CN.Observer */ CN.Observer.haveFired = []; CN.Observer.prototype = { /** * Subscribes to an observable event * @method subscribe * * @param fn {Function} A function to execute when the subscribed event fires */ subscribe : function(fn) { this.fns.push(fn); }, /** * Unsubscribes to an observable event * @method unsubscribe * * @param fn {Function} A function to remove from those executed when the subscribed event fires */ unsubscribe : function(fn) { this.fns = this.fns.filter( function(el) { if (el !== fn) { return el; } } ); }, /** * Executes the functions bound to the observable * @method fire * * @param o {Object} Parameters to pass to the functions when they are called * @param scope {Object} Optional scope to execute function within, defaults to window */ fire : function(o, thisObj) { var scope = thisObj || window; this.fns.forEach(function(el) { el.call(scope, o); }); if (this.label && CN.Observer.haveFired.indexOf(this.label) === -1) { CN.Observer.haveFired.push(this.label); } } }; /** * Creates a timer * Adapted from GNU licensed JavaScript Timer * Original API Docs: * Pass in the milliseconds to wait and the callback function to assign. * Timer functions are chainable, and can be started, stopped, paused, resumed and restarted. * @class Timer * @constructor * * @param millis {Number} Milliseconds for the timer * @param callback {Function} A callback to execute each time the interval is reached */ CN.Timer = function(millis, callback) { this.interval = millis; this.timer = null; this.callbacks = []; this.multipliers = []; this.tickCounts = []; this.canRun = []; this.stoppedThreads = 0; this.shouldRunOnce = false; this.startedAt = -1; this.pausedAt = -1; this.addCallback(callback); return this; }; CN.Timer.prototype = { preset : function() { // called from start() this.stoppedThreads = 0; this.startedAt = -1; this.pausedAt = -1; for (var i = 0, il = this.callbacks.length; i < il; i++) { this.canRun[i] = true; this.tickCounts[i] = 0; } }, ticks : function(initInterval) { var that = this, i, il; for (i = 0, il = this.callbacks.length; i < il; i++) { if (typeof this.callbacks[i] === 'function' && this.canRun[i]) { this.tickCounts[i]++; if (this.tickCounts[i] === this.multipliers[i]) { this.tickCounts[i] = 0; if (this.runOnce()) { this.canRun[i] = false; this.stoppedThreads++; } window.setTimeout(that.callbacks[i], 0); } } } if (this.runOnce() && this.stoppedThreads === this.callbacks.length) { this.stop(); } if (typeof initInterval === 'number') { this.stop().start(null, true); } }, runOnce : function(isRunOnce) { if (typeof isRunOnce === 'undefined') { return this.shouldRunOnce; } else if (typeof isRunOnce === 'boolean') { this.shouldRunOnce = isRunOnce; } else { CN.logger.getInstance().log.error('Invalid argument for runOnce'); } return this; }, /** * Resets the interval to the specified time or returns the current interval setting * @method getSetInterval * * @param millis {Number} Milliseconds to change the timer's interval to * * @return {Mixed} Either the current interval or the timer object itself after resetting the interval */ getSetInterval : function(millis) { if (typeof millis === 'undefined') { return this.interval; } else if (typeof millis === 'number') { this.interval = Math.floor(millis); } return this; }, /** * Stops the timer. * @method stop */ stop : function(isPausing) { if (this.timer) { if (!isPausing) { this.pausedAt = -1; } try { window.clearInterval(this.timer); } catch(e) { } this.timer = null; } return this; }, isStopped : function() { return ((this.timer === null) && !this.isPaused()); }, /** * Starts the timer * @method start */ start : function(initialInterval, withoutPreset) { // don't use params when calling var tempInterval, that = this; if (this.isPaused()) { return this.resume(); } if (!this.isStopped()) { return this; } if (!withoutPreset) { this.preset(); } tempInterval = this.interval; if (typeof initialInterval === 'number') { tempInterval = initialInterval; } this.timer = window.setInterval(function() { that.ticks(initialInterval); }, tempInterval); this.startedAt = new Date().getTime(); this.startedAt -= (this.interval - tempInterval); return this; }, /** * Pauses the timer, without resetting. Use resume() to restart playing. * @method pause */ pause : function() { if (this.timer) { this.pausedAt = new Date().getTime(); this.stop(true); } return this; }, isPaused : function() { return (this.pausedAt >= 0); }, /** * Resumes playing a paused timer * @method resume */ resume : function() { if (this.isPaused()) { var tempInterval = this.interval - ((this.pausedAt - this.startedAt) % this.interval); this.pausedAt = -1; this.start(tempInterval, true); } return this; }, restart : function() { return this.stop().start(); }, /** * Adds a callback to the array to be executed at the timer's interval * @method addCallback */ addCallback : function(callback, n) { if (typeof callback === 'function') { this.callbacks.push(callback); if (typeof n === 'number') { n = Math.floor(n); this.multipliers.push(n < 1 ? 1 : n); } else { this.multipliers.push(1); } this.tickCounts.push(0); this.canRun.push(true); } return this; }, clearCallbacks : function() { this.callbacks.length = 0; this.multipliers.length = 0; this.canRun.length = 0; this.tickCounts.length = 0; this.stoppedThreads = 0; return this; } }; /** * DOM-related methods * @class dom * @static */ CN.dom = CN.dom || {}; /** * Temporary storage for DOM elements * @property storage * @static */ CN.dom.storage = { activeClass : 'active', inactiveClass : 'inactive', innerTag : 'span' }; /** * Removes an element temporarily from the document tree ('activating' a tab, for example, by removing its link) * @method activateElement * * @param el {Node} A standard DOM element * @param storage {Object} A temporary storage variable * @param klass {String} An optional class name to apply * * @return {Object} The storage variable, now with the element added */ CN.dom.storage.activateElement = function(el, storage, klass) { var oldLink, newLink, newEl; // Uses the default class name unless one is provided klass = klass || this.activeClass; // If there is a link present in the element if (el.getElementsByTagName('a').length > 0) { // Stores the link - Note that the cloning of the element is // because of an IE bug where the links get frozen on the hover // state. Creating a new element altogether fixes this bug. For // good browsers, we can just remove the link to storage. if (jQuery.browser.msie || jQuery.browser.safari) { oldLink = jQuery(el.getElementsByTagName('a')[0]); newLink = oldLink.clone(true); storage = jQuery(newLink).get()[0]; oldLink.remove(); } else { storage = el.removeChild(el.getElementsByTagName('a')[0]); } jQuery(el).addClass(klass); // Creates a new span to hold the contents and aid in styling newEl = document.createElement(this.innerTag); newEl.innerHTML = storage.innerHTML; // Appends the span to the element and returns the storage variable reference el.appendChild(newEl); return storage; } }; /** * Reinserts an element temporarily from the document tree * @method deactivateElement * @static * * @param el {Node} A standard DOM element * @param storage {Object} A temporary storage variable * @param klass {String} An optional class name to apply */ CN.dom.storage.deactivateElement = function(el, storage, klass) { // Uses the default class name unless one is provided klass = klass || this.activeClass; // If there is a span inside the element if (el.getElementsByTagName(this.innerTag).length > 0) { // Gets the span element, remove it and the class name, and add back what is in storage var children = el.getElementsByTagName(this.innerTag); el.removeChild(children[0]); el.appendChild(storage); jQuery(el).removeClass(klass); } }; /** * Queues up function calls. Pass it a string of the function name along with an array of * args and it will execute those functions when calling the execute() method. * Used for Omniture on forums to overcome problems with ordering of script includes. * @todo Add support for multiple queues. Any way to remove the eval from this? * @param {mixed} mixed The object being tested * @return {boolean} the result */ CN.functionQueue = (function() { var q = []; return { /** * Add functions to a queue to execute later. * @param {string} [f] Function to call * @param {array} [a] Array of args to pass to function. * @param {string} [qId] Not implemented but would be used to support multiple queues of functions. */ addToQueue : function (f, a, qId) { var temp = {fName:f, args:a}; q.push(temp); }, /** * Executes functions in the queue. * @param {string} [qId] Note implemented but would execute functions in a specific queue. Would allow for multiple queues. */ execute : function (qId) { var l = q.length, i, tempF; for (i=0; i