/** * This is the Code 42 Software application extension to jQuery. jQuery must * already be loaded This includes: auth : authentication url : URL manipulation * routines safe : null-safe value routines date : date parsing routines nav : * nav routines */ // ////////////////////////////////////////////////////////////////////////////// // // Browser validation code - Jquery 1.2.x does *not* work in Safari 2 or // older!!! // alert($.browser.safari + $.browser.version); // // ////////////////////////////////////////////////////////////////////////////// if ($.browser.safari && $.browser.version < 522) { top.location.href = "incompatible.html"; } // ////////////////////////////////////////////////////////////////////////////// // // Required native Javascript / JQuery extensions // // ////////////////////////////////////////////////////////////////////////////// // Date formatting Date.prototype.format = function(format) { var returnStr = ''; var replace = Date.replaceChars; for ( var i = 0; i < format.length; i++) { var curChar = format.charAt(i); if (replace[curChar]) { returnStr += replace[curChar].call(this); } else { returnStr += curChar; } } return returnStr; }; Date.replaceChars = { shortMonths : [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], longMonths : [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], shortDays : [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], longDays : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], d : function() { return (this.getDate() < 10 ? '0' : '') + this.getDate(); }, D : function() { return Date.replaceChars.shortDays[this.getDay()]; }, j : function() { return this.getDate(); }, l : function() { return Date.replaceChars.longDays[this.getDay()]; }, N : function() { return this.getDay() + 1; }, S : function() { return (this.getDate() % 10 == 1 && this.getDate() != 11 ? 'st' : (this .getDate() % 10 == 2 && this.getDate() != 12 ? 'nd' : (this.getDate() % 10 == 3 && this.getDate() != 13 ? 'rd' : 'th'))); }, w : function() { return this.getDay(); }, z : function() { return "Not Yet Supported"; }, W : function() { return "Not Yet Supported"; }, F : function() { return Date.replaceChars.longMonths[this.getMonth()]; }, m : function() { return (this.getMonth() < 9 ? '0' : '') + (this.getMonth() + 1); }, M : function() { return Date.replaceChars.shortMonths[this.getMonth()]; }, n : function() { return this.getMonth() + 1; }, t : function() { return "Not Yet Supported"; }, L : function() { return "Not Yet Supported"; }, o : function() { return "Not Supported"; }, Y : function() { return this.getFullYear(); }, y : function() { return ('' + this.getFullYear()).substr(2); }, a : function() { return this.getHours() < 12 ? 'am' : 'pm'; }, A : function() { return this.getHours() < 12 ? 'AM' : 'PM'; }, B : function() { return "Not Yet Supported"; }, g : function() { return this.getHours() % 12 || 12; }, G : function() { return this.getHours(); }, h : function() { return ((this.getHours() % 12 || 12) < 10 ? '0' : '') + (this.getHours() % 12 || 12); }, H : function() { return (this.getHours() < 10 ? '0' : '') + this.getHours(); }, i : function() { return (this.getMinutes() < 10 ? '0' : '') + this.getMinutes(); }, s : function() { return (this.getSeconds() < 10 ? '0' : '') + this.getSeconds(); }, e : function() { return "Not Yet Supported"; }, I : function() { return "Not Supported"; }, O : function() { return (-this.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(this.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(this.getTimezoneOffset() / 60)) + '00'; }, T : function() { var m = this.getMonth(); this.setMonth(0); var result = this.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); this.setMonth(m); return result; }, Z : function() { return -this.getTimezoneOffset() * 60; }, c : function() { return "Not Yet Supported"; }, r : function() { return this.toString(); }, U : function() { return this.getTime() / 1000; } }; // Parse ISO8601 formats to Dates Date.parseISO8601 = function(dString) { var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T|\s)?(\d\d)(:)?(\d\d)(:)?(\d\d)(\.\d+)?(Z|([+-])(\d\d)(:)?(\d\d))/; var _date = new Date(); if (dString.toString().match(new RegExp(regexp))) { var d = dString.match(new RegExp(regexp)); var offset = 0; _date.setUTCDate(1); _date.setUTCFullYear(parseInt(d[1], 10)); _date.setUTCMonth(parseInt(d[3], 10) - 1); _date.setUTCDate(parseInt(d[5], 10)); _date.setUTCHours(parseInt(d[7], 10)); _date.setUTCMinutes(parseInt(d[9], 10)); _date.setUTCSeconds(parseInt(d[11], 10)); if (d[12]) _date.setUTCMilliseconds(parseFloat(d[12]) * 1000); else _date.setUTCMilliseconds(0); if (d[13] != 'Z') { offset = (d[15] * 60) + parseInt(d[17], 10); offset *= ((d[14] == '-') ? -1 : 1); _date.setTime(_date.getTime() - offset * 60 * 1000); } } else { _date.setTime(Date.parse(dString)); } return _date; } // Add fadeToggle to jQuery $.fn.fadeToggle = function(speed, easing, callback) { return this.animate( { opacity : 'toggle' }, speed, easing, callback); }; // ////////////////////////////////////////////////////////////////////////////// // // Start c42 library // // ////////////////////////////////////////////////////////////////////////////// if (!$.c42) { $.c42 = {}; // Create the c42 plugin namespace if it is not already } // ////////////////////////////////////////////////////////////////////////////// // // c42 Script Loader // This is essential to loading scripts and making sure they load out of // a common location // // ////////////////////////////////////////////////////////////////////////////// if (!$.c42.scriptHome) { $.c42.scriptHome = "../common/js"; // Create the base url directory for // loading scripts } // ////////////////////////////////////////////////////////////////////////////// // // Error logging extensions // // $.c42.log : Log a message to the error console // // ////////////////////////////////////////////////////////////////////////////// $.c42.logError = function(msg) { throw msg; } $.c42.log = function(msg) { if (console && console.log) { console.log(msg); // Firebug, Safari, etc } else if (Components && Components.utils && Components.utils.reportError) { Components.utils.reportError(msg); // Firefox sans Firebug } else if (opera && opera.postError) { opera.postError(msg); // Opera } else { throw msg; } } /** * Load a remote script on demand only if it hasn't been loaded. This is * primarily used internally by the c42 framework for managing javascript * library dependencies */ $.c42.load = function(url) { if ($.c42._includeTable == undefined) { $.c42._includeTable = {}; } if ($.c42._includeTable[url] == undefined) { if (url.indexOf("/") == -1) { url = $.c42.scriptHome + "/" + url; } $.c42._includeTable[url] = true; $.ajax( { url : url, type : "GET", async : false, dataType : "script", error : function(request, status, error) { // This used to be an alert, but we don't want non-developers // being alarmed about it $.c42.logError("Unable to load " + url); } }) } } $.c42.load("jquery.inc.pack.js"); // always include client side include // ////////////////////////////////////////////////////////////////////////////// // // Authentication extensions // The following library extension handles standard Code 42 authentication // via REST. // // $c42.authData - This is your authentication session data // $c42.authToken() - Call this to perform token based authentication // $c42.auth() - Call this to perform username/password authentication // $c42.deauth() - Deauthorize a session // // ////////////////////////////////////////////////////////////////////////////// /** * Authentication constants */ $.c42.AUTH_COOKIE_KEY = "user"; $.c42.AUTH_HEADER_TOKEN = "TOKEN "; $.c42.AUTH_HEADER_BASIC = "BASIC "; $.c42.AUTH_REST_URL = "/rest/auth"; /** * You can check the authData from an authentication request using this object */ $.c42.authData = { set : function(data) { // Set the authentication information this.username = data.username; this.userId = data.userId; this.authToken = data.authToken; this.orgId = data.orgId; this.viewAllOrgs = data.viewAllOrgs; this.sysAdmin = data.sysAdmin; this.orgAdmin = data.orgAdmin; this.orgManager = data.orgManager; this.partner = data.partner; // $.c42.load("jquery.c42.inspect.js"); // $.c42.inspect.alert(this, 7); } }; /** * Check if session is authorized. If we have cookie authentication enabled, * then we can check for the C42 authentication token. If the session is not * authenticated, we redirect to the unauthorized URL. * * This method is synchronous * * @param unathorizedURL - * URL to redirect to if session is unauthorized * @param isAdmin42 - * true if we need to verify that in the CrashPlan org only admins * are allowed */ $.c42.authToken = function(unauthorizedURL, isAdmin42) { $.c42.load('jquery.cookie.js'); // need cookie lib if (!unauthorizedURL) { throw new Error("c42.auth() unauthorizedURL undefined"); } // Check if we have a valid cookie - if not, redirect to unauthorizedURL var key = $.cookie($.c42.AUTH_COOKIE_KEY); if (key) { var auth = $.c42.AUTH_HEADER_TOKEN + key; $.ajax( { url : $.c42.AUTH_REST_URL, type : "GET", async : false, dataType : "json", contentType : "application/json", beforeSend : function(req) { $.c42.setAuthHeader(req, auth); }, success : function(data, textStatus) { $.c42.authData.set(data); if (isAdmin42 && $.c42.authData.orgId == 42 && !$.c42.authData.orgAdmin) { document.location.href = unauthorizedURL; // unauthorized // for admin42 requirement } }, error : function(request, status, error) { $.c42.logError("Unauthorized " + request + ":" + status + ":" + error); document.location.href = unauthorizedURL; // unauthorized } }) } else { // no cookie set - go to unauthorized URL document.location.href = unauthorizedURL; // unauthorized } }; /** * Attempt to authenticate a username and password. Asynch. Requires callback * functions. * * @param username * Username to auth * @param password * @param successCb * Callback function if auth is successful * @param errorCb * Callback function if auth fails */ $.c42.auth = function(username, password, successCb, errorCb, isAdmin42) { $.c42.load('base64.js'); // include base64 library $.c42.load('jquery.cookie.js'); // need cookie lib var auth = username + ":" + password; auth = $.c42.AUTH_HEADER_BASIC + Base64.encode(auth); errorCallback = function(xhr, status, error) { if (errorCb) { errorCb(xhr, status, error); return; } if (xhr.status == 503) { if (xhr.getResponseHeader("Retry-After")) { alert("LDAP lookup is slow. Try again in a minute."); } else { alert("Login service is unavailable. (503)"); } } else if (xhr.status == 401) { alert("Your email address and/or password is invalid. Please try again."); } else if (xhr.status == 404) { alert("Login service is unavailable. (404)"); } else { alert('Error during login: statusCode: ' + xhr.status); } }; $.ajax( { url : $.c42.AUTH_REST_URL, type : "GET", dataType : "json", contentType : "application/json", beforeSend : function(req) { $.c42.setAuthHeader(req, auth); }, success : function(data, textStatus) { // Set the authentication information $.c42.authData.set(data); if (isAdmin42 && !$.c42.authData.orgAdmin && $.c42.authData.orgId == 42) { errorCallback(); return; } // Set the authentication cookie $.cookie($.c42.AUTH_COOKIE_KEY, data.authToken, $.c42 .authCookieOptions()); // set the cookie for auto-auth if (successCb) { successCb(data, textStatus); } }, error : errorCallback }); }; /** * De-authorize / Sign out! Calling this will remove the authorization cookie. * and then redirect user to an unauthorized landing url */ $.c42.deauth = function(unauthorizedURL) { $.c42.load('jquery.cookie.js'); // need cookie lib // Set the cookie to null and set to expire in the past (Firefox seems to need the expire portion). var date = new Date(); date.setTime(0); // Jan 1, 1970 $.cookie($.c42.AUTH_COOKIE_KEY, null, {path:'/', expires:date}); if (unauthorizedURL != undefined) { document.location.href = unauthorizedURL; // redirect to unauthorized // url } return false; } /** * Provide this as the 'beforeSend' function to auto-login on REST services * * @param req * The request object */ $.c42.authBeforeSend = function(req) { $.c42.load('jquery.cookie.js'); // need cookie lib var authToken = $.cookie($.c42.AUTH_COOKIE_KEY); if (authToken) { var auth = $.c42.AUTH_HEADER_TOKEN + authToken; $.c42.setAuthHeader(req, auth); } } /** * Private method for setting the Authorization header */ $.c42.setAuthHeader = function(req, auth) { req.setRequestHeader("Authorization-Challenge", "false"); // no challenge! if ((navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))) { // Safari on iPhone/iPod barfs on the "Authorization" header so use // "Auth" instead. // http://www.devilx.net/2009/10/23/iphone-safari-and-xmlhttprequest-authorization-headers req.setRequestHeader("Auth", auth); } else { req.setRequestHeader("Authorization", auth); } } /** * Provides options so the cookie expires 30 minutes from now */ $.c42.authCookieOptions = function() { var date = new Date(); date.setTime(date.getTime() + (30 * 60 * 1000)/* 30 minutes */); $.c42.AUTH_COOKIE_OPTIONS = { path : '/', expires : date }; // alert("Expires: " + date); return $.c42.AUTH_COOKIE_OPTIONS; } /** * Refresh the expiration date on the auth cookie for 30 minutes from now */ $.c42.authCookieRefresh = function() { $.c42.load('jquery.cookie.js'); // need cookie lib var authToken = $.cookie($.c42.AUTH_COOKIE_KEY); if (authToken) { var options = $.c42.authCookieOptions(); // alert("Auth cookie will expire at: " + options.expires); $.cookie($.c42.AUTH_COOKIE_KEY, authToken, options); } } // Refresh the cookie expire time every time we load this script $.c42.authCookieRefresh(); // ////////////////////////////////////////////////////////////////////////////// // // AJAX Transport extensions // // $.c42.load : Load a script ONCE and only ONCE // $.c42.postJSON : Post object as JSON // // ////////////////////////////////////////////////////////////////////////////// /** * Post an object as a JSON payload to a remote URL * * @param obj * The object containing custom Ajax options and 'data' to JSONize */ $.c42.postJSON = function(obj) { $.c42.load('json2.js'); // make sure that we load the correct json lib var block = {} block.url = obj.url block.success = obj.success if (obj.error) { block.error = obj.error } block.timeout = 30000 /* Default value of 30000ms = 30 sec */ if (obj.timeout) { block.timeout = obj.timeout } block.type = "POST" block.contentType = "application/json" block.dataType = "json" // Note the use of an external JSON parser here. If JQuery is passed a raw // data object it will attempt to // "serialize" it, which basically amounts to an encoding resembling a GET // query string. There wasn't any // obvious way to make it use JSON for the encoding, so we had to import an // external processor. The "json" // string in the syscall indicates the expected return type and not the // expected encoding for the outgoing // request. block.data = JSON.stringify(obj.data) $.ajax(block) } /** * Post an object as a JSON payload to a remote URL * * @param obj * The object containing custom Ajax options and 'data' to JSONize */ $.c42.putJSON = function(obj) { $.c42.load('json2.js'); // make sure that we load the correct json lib var block = {} block.url = obj.url block.success = obj.success if (obj.error) { block.error = obj.error } block.timeout = 30000 /* Default value of 30000ms = 30 sec */ if (obj.timeout) { block.timeout = obj.timeout } block.type = "PUT" block.contentType = "application/json" block.dataType = "json" // Note the use of an external JSON parser here. If JQuery is passed a raw // data object it will attempt to // "serialize" it, which basically amounts to an encoding resembling a GET // query string. There wasn't any // obvious way to make it use JSON for the encoding, so we had to import an // external processor. The "json" // string in the syscall indicates the expected return type and not the // expected encoding for the outgoing // request. block.data = JSON.stringify(obj.data) $.ajax(block) } /** * Encapsulate all of the values in a form in a single object * * @param id * The id of the form to select from * @return data object containing element id,value pairs */ $.c42.getFormData = function(id) { // Select all input or select elements underneath the named form var selector = "form#" + id + " input,select,textarea"; var data = {}; $(selector).each(function(i) { var name = this.id if (!name) { name = $(this).attr('name'); } if (name) { data[name] = $(this).val(); } }); return data } // ////////////////////////////////////////////////////////////////////////////// // // URL manipulation and redirect extensions // The following library extension handles standard Code 42 URL manipulation // // $.c42.redirect // $.c42.redirectSSL // $.c42.getResourceName // ////////////////////////////////////////////////////////////////////////////// /** * Redirect to a relative URL on a new host IF the required hostname is present */ $.c42.redirect = function(requiredHostname, targetHostname) { if (document.location.host.indexOf(requiredHostname) != -1) { var l = document.location; var url = l.protocol + "//" + targetHostname + l.pathname; if (l.hash) url += l.hash; if (l.search) url += l.search; document.location = url; } } /** * Redirect to an HTTPS url if you match the current hostname * * @param requiredHostname * @return */ $.c42.redirectSSL = function(requiredHostname, targetHostname) { var l = document.location; if (l.host.indexOf(requiredHostname) != -1) { if (l.protocol.indexOf("http:") != -1) { var host = targetHostname == undefined ? l.host : targetHostname; var url = "https://" + host + l.pathname; if (l.hash) url += l.hash; if (l.search) url += l.search; document.location = url; } } } /** * Redirect to an HTTPS url if you match the current hostname TODO - Refactor to * take in a protocol parameter switch * * @param requiredHostname * @return */ $.c42.redirectNONSSL = function(requiredHostname, targetHostname) { var l = document.location; if (l.host.indexOf(requiredHostname) != -1) { if (l.protocol.indexOf("https:") != -1) { var host = targetHostname == undefined ? l.host : targetHostname; var url = "http://" + host + l.pathname; if (l.hash) url += l.hash; if (l.search) url += l.search; document.location = url; } } } /** * Returns just the resource portion of the URL */ $.c42.getResourceName = function() { var file_name = document.location.href; var end = (file_name.indexOf("?") == -1) ? file_name.length : file_name .indexOf("?"); end = (file_name.indexOf("#") == -1) ? end : file_name.indexOf("#"); // chop // anchors // too return file_name.substring(file_name.lastIndexOf("/") + 1, end); } // ////////////////////////////////////////////////////////////////////////////// // // This is the Code 42 Software application extension to jQuery // for safe string operations. i.e. if the string is null or undefined // // ////////////////////////////////////////////////////////////////////////////// $.c42.safe = function(value) { return value ? value : " "; } $.c42.safeTimestamp = function(ts) { return ts ? ts.substring(0, 16).replace("T", " ") : " "; } $.c42.safeDate = function(ts, formatStr) { if (ts) { if (!formatStr) formatStr = "m/d/Y"; // "m/d/Y h:iA"; var d = Date.parseISO8601(ts); return d.format(formatStr); } return " "; } $.c42.safeDateTime = function(ts) { return $.c42.safeDate(ts, "m/d/Y h:iA"); } // ////////////////////////////////////////////////////////////////////////////// // // This is the Code 42 Software application extension to jQuery // for status. Requires base.css and std.css // // ////////////////////////////////////////////////////////////////////////////// /** * Sets a normal informational message */ $.c42.setStatus = function(text) { $("#status").removeClass("status-error"); $.c42._setStatus(text); } /** * Sets an error message */ $.c42.setStatusError = function(text) { $("#status").addClass("status-error"); $.c42._setStatus(text, true); } /** * Close the status element */ $.c42.closeStatus = function() { $("#status").slideUp("fast"); } /** * Private helper function for actually setting the status text Hides the status * after a few seconds by default. */ $.c42._setStatus = function(text, isError) { // Display the status widget if ($("#status").length > 0) { $("#status").html( text + "
Click to close"); if (!$("#status").is(":visible")) { $("#status").slideDown("fast"); } } } /** * Initialize status element if it exists Attach a click handler to close the * status */ $(document).ready(function() { if ($("#status").length > 0) { // status element exists $("#status").click($.c42.closeStatus); } }); // ////////////////////////////////////////////////////////////////////////////// // // This is the Code 42 Software application extension to jQuery // for page navigation controls. Requires base.css and std.css // // ////////////////////////////////////////////////////////////////////////////// $.c42.nav = {}; /** * Selected nav items are set by matching the start of the resource with the nav * item. For example, anything starting with 'download' will match the nav item * 'download'. */ $.c42.nav.init = function() { var resourceName = $.c42.getResourceName(); $("#nav").find("a").each(function() { // Toggle selected state if (resourceName.indexOf(this.id) == 0) { // starts with id anchor $("#" + this.id).parent("li").addClass("selected"); } }); // Assign click, hover support $("#nav > ul > li").each(function() { var anchor = $(this).find("a:first"); $.c42.nav.setClick(this, anchor); $.c42.nav.setHover(this); }); // Assign home click support $.c42.nav.setClick($("#home")); // Customize search box for safari if ($.browser.safari) { $("#search-input").attr("results", "5"); } } /** * Selected menu items are set by matching resource */ $.c42.nav.menuInit = function() { var resourceName = $.c42.getResourceName(); var el = $(".menu").find("a[href='" + resourceName + "']:first"); if (el) { // match the element whose href is the current document el.parent("li").addClass("selected"); } // Add hover support // Add click 'anywhere support $(".menu").find("li").each(function() { var anchor = $(this).find("a:first"); $(this).hover(function() { $(this).addClass("hover"); anchor.addClass("hover"); }, function() { $(this).removeClass("hover"); anchor.removeClass("hover"); }); if (!$(this).hasClass("ignore")) { $.c42.nav.setClick(this, anchor); } }); } /** * Set an item click to the same href as an anchor */ $.c42.nav.setClick = function(item, anchor) { var href = anchor == undefined ? "index.html" : $(anchor).attr("href"); $(item).click(function() { top.location.href = href; }); } /** * Assign a hover over effect for list items */ $.c42.nav.setHover = function(item) { if (!$(item).hasClass("selected")) { $(item).hover(function() { $(item).addClass("hover"); }, function() { $(item).removeClass("hover"); }); } } /** * Always returns an integer object (or whatever the default is). * @param name - query param name * @param defValue - default value */ $.c42.getQueryInt = function(name, defValue) { $.c42.load('jquery.query.js'); var value = $.query.get(name); if (value && value != true && /\d/.test(value)) { return parseInt(value); } return defValue; } /** * Always returns a String object (or whatever the default is). * @param name - query param name * @param defValue - default value * @param allowEmpty - if value is empty, return empty instead of the default */ $.c42.getQueryString = function(name, defValue, allowEmpty) { $.c42.load('jquery.query.js'); var value = $.query.get(name); if (value == true) { // The query param value is an empty string return allowEmpty ? '' : defValue; } if (value) { return value; } return defValue; } /** * Always returns a boolean object (or whatever the default is). * @param name - query param name * @param defValue - default value */ $.c42.getQueryBoolean = function(name, defValue) { $.c42.load('jquery.query.js'); var value = $.query.get(name); if (value === true) { // The query param value is an empty string return defValue; } if (value) { // no type conversions in this check. return value === 'true'; } return defValue; }