/** * easyXDM * http://easyxdm.net/ * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ (function (window, document, location, setTimeout, decodeURIComponent, encodeURIComponent) { /*jslint evil: true, browser: true, immed: true, passfail: true, undef: true, newcap: true*/ /*global JSON, XMLHttpRequest, window, escape, unescape, ActiveXObject */ // // easyXDM // http://easyxdm.net/ // Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // var global = this; var channelId = Math.floor(Math.random() * 10000); // randomize the initial id in case of multiple closures loaded var emptyFn = Function.prototype; var reURI = /^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/; // returns groups for protocol (2), domain (3) and port (4) var reParent = /[\-\w]+\/\.\.\//; // matches a foo/../ expression var reDoubleSlash = /([^:])\/\//g; // matches // anywhere but in the protocol var namespace = ""; // stores namespace under which easyXDM object is stored on the page (empty if object is global) var easyXDM = {}; var _easyXDM = window.easyXDM; // map over global easyXDM in case of overwrite var IFRAME_PREFIX = "easyXDM_"; var HAS_NAME_PROPERTY_BUG; var useHash = false; // whether to use the hash over the query var flashVersion; // will be set if using flash var HAS_FLASH_THROTTLED_BUG; // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting function isHostMethod(object, property){ var t = typeof object[property]; return t == 'function' || (!!(t == 'object' && object[property])) || t == 'unknown'; } function isHostObject(object, property){ return !!(typeof(object[property]) == 'object' && object[property]); } // end // http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/ function isArray(o){ return Object.prototype.toString.call(o) === '[object Array]'; } // end function hasFlash(){ var name = "Shockwave Flash", mimeType = "application/x-shockwave-flash"; if (!undef(navigator.plugins) && typeof navigator.plugins[name] == "object") { // adapted from the swfobject code var description = navigator.plugins[name].description; if (description && !undef(navigator.mimeTypes) && navigator.mimeTypes[mimeType] && navigator.mimeTypes[mimeType].enabledPlugin) { flashVersion = description.match(/\d+/g); } } if (!flashVersion) { var flash; try { flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); flashVersion = Array.prototype.slice.call(flash.GetVariable("$version").match(/(\d+),(\d+),(\d+),(\d+)/), 1); flash = null; } catch (notSupportedException) { } } if (!flashVersion) { return false; } var major = parseInt(flashVersion[0], 10), minor = parseInt(flashVersion[1], 10); HAS_FLASH_THROTTLED_BUG = major > 9 && minor > 0; return true; } /* * Cross Browser implementation for adding and removing event listeners. */ var on, un; if (isHostMethod(window, "addEventListener")) { on = function(target, type, listener){ target.addEventListener(type, listener, false); }; un = function(target, type, listener){ target.removeEventListener(type, listener, false); }; } else if (isHostMethod(window, "attachEvent")) { on = function(object, sEvent, fpNotify){ object.attachEvent("on" + sEvent, fpNotify); }; un = function(object, sEvent, fpNotify){ object.detachEvent("on" + sEvent, fpNotify); }; } else { throw new Error("Browser not supported"); } /* * Cross Browser implementation of DOMContentLoaded. */ var domIsReady = false, domReadyQueue = [], readyState; if ("readyState" in document) { // If browser is WebKit-powered, check for both 'loaded' (legacy browsers) and // 'interactive' (HTML5 specs, recent WebKit builds) states. // https://bugs.webkit.org/show_bug.cgi?id=45119 readyState = document.readyState; domIsReady = readyState == "complete" || (~ navigator.userAgent.indexOf('AppleWebKit/') && (readyState == "loaded" || readyState == "interactive")); } else { // If readyState is not supported in the browser, then in order to be able to fire whenReady functions apropriately // when added dynamically _after_ DOM load, we have to deduce wether the DOM is ready or not. // We only need a body to add elements to, so the existence of document.body is enough for us. domIsReady = !!document.body; } function dom_onReady(){ if (domIsReady) { return; } domIsReady = true; for (var i = 0; i < domReadyQueue.length; i++) { domReadyQueue[i](); } domReadyQueue.length = 0; } if (!domIsReady) { if (isHostMethod(window, "addEventListener")) { on(document, "DOMContentLoaded", dom_onReady); } else { on(document, "readystatechange", function(){ if (document.readyState == "complete") { dom_onReady(); } }); if (document.documentElement.doScroll && window === top) { var doScrollCheck = function(){ if (domIsReady) { return; } // http://javascript.nwbox.com/IEContentLoaded/ try { document.documentElement.doScroll("left"); } catch (e) { setTimeout(doScrollCheck, 1); return; } dom_onReady(); }; doScrollCheck(); } } // A fallback to window.onload, that will always work on(window, "load", dom_onReady); } /** * This will add a function to the queue of functions to be run once the DOM reaches a ready state. * If functions are added after this event then they will be executed immediately. * @param {function} fn The function to add * @param {Object} scope An optional scope for the function to be called with. */ function whenReady(fn, scope){ if (domIsReady) { fn.call(scope); return; } domReadyQueue.push(function(){ fn.call(scope); }); } /** * Returns an instance of easyXDM from the parent window with * respect to the namespace. * * @return An instance of easyXDM (in the parent window) */ function getParentObject(){ var obj = parent; if (namespace !== "") { for (var i = 0, ii = namespace.split("."); i < ii.length; i++) { obj = obj[ii[i]]; } } return obj.easyXDM; } /** * Removes easyXDM variable from the global scope. It also returns control * of the easyXDM variable to whatever code used it before. * * @param {String} ns A string representation of an object that will hold * an instance of easyXDM. * @return An instance of easyXDM */ function noConflict(ns){ window.easyXDM = _easyXDM; namespace = ns; if (namespace) { IFRAME_PREFIX = "easyXDM_" + namespace.replace(".", "_") + "_"; } return easyXDM; } /* * Methods for working with URLs */ /** * Get the domain name from a url. * @param {String} url The url to extract the domain from. * @return The domain part of the url. * @type {String} */ function getDomainName(url){ return url.match(reURI)[3]; } /** * Get the port for a given URL, or "" if none * @param {String} url The url to extract the port from. * @return The port part of the url. * @type {String} */ function getPort(url){ return url.match(reURI)[4] || ""; } /** * Returns a string containing the schema, domain and if present the port * @param {String} url The url to extract the location from * @return {String} The location part of the url */ function getLocation(url){ var m = url.toLowerCase().match(reURI); var proto = m[2], domain = m[3], port = m[4] || ""; if ((proto == "http:" && port == ":80") || (proto == "https:" && port == ":443")) { port = ""; } return proto + "//" + domain + port; } /** * Resolves a relative url into an absolute one. * @param {String} url The path to resolve. * @return {String} The resolved url. */ function resolveUrl(url){ // replace all // except the one in proto with / url = url.replace(reDoubleSlash, "$1/"); // If the url is a valid url we do nothing if (!url.match(/^(http||https):\/\//)) { // If this is a relative path var path = (url.substring(0, 1) === "/") ? "" : location.pathname; if (path.substring(path.length - 1) !== "/") { path = path.substring(0, path.lastIndexOf("/") + 1); } url = location.protocol + "//" + location.host + path + url; } // reduce all 'xyz/../' to just '' while (reParent.test(url)) { url = url.replace(reParent, ""); } return url; } /** * Appends the parameters to the given url.
* The base url can contain existing query parameters. * @param {String} url The base url. * @param {Object} parameters The parameters to add. * @return {String} A new valid url with the parameters appended. */ function appendQueryParameters(url, parameters){ var hash = "", indexOf = url.indexOf("#"); if (indexOf !== -1) { hash = url.substring(indexOf); url = url.substring(0, indexOf); } var q = []; for (var key in parameters) { if (parameters.hasOwnProperty(key)) { q.push(key + "=" + encodeURIComponent(parameters[key])); } } return url + (useHash ? "#" : (url.indexOf("?") == -1 ? "?" : "&")) + q.join("&") + hash; } // build the query object either from location.query, if it contains the xdm_e argument, or from location.hash var query = (function(input){ input = input.substring(1).split("&"); var data = {}, pair, i = input.length; while (i--) { pair = input[i].split("="); data[pair[0]] = decodeURIComponent(pair[1]); } return data; }(/xdm_e=/.test(location.search) ? location.search : location.hash)); /* * Helper methods */ /** * Helper for checking if a variable/property is undefined * @param {Object} v The variable to test * @return {Boolean} True if the passed variable is undefined */ function undef(v){ return typeof v === "undefined"; } /** * A safe implementation of HTML5 JSON. Feature testing is used to make sure the implementation works. * @return {JSON} A valid JSON conforming object, or null if not found. */ var getJSON = function(){ var cached = {}; var obj = { a: [1, 2, 3] }, json = "{\"a\":[1,2,3]}"; if (typeof JSON != "undefined" && typeof JSON.stringify === "function" && JSON.stringify(obj).replace((/\s/g), "") === json) { // this is a working JSON instance return JSON; } if (Object.toJSON) { if (Object.toJSON(obj).replace((/\s/g), "") === json) { // this is a working stringify method cached.stringify = Object.toJSON; } } if (typeof String.prototype.evalJSON === "function") { obj = json.evalJSON(); if (obj.a && obj.a.length === 3 && obj.a[2] === 3) { // this is a working parse method cached.parse = function(str){ return str.evalJSON(); }; } } if (cached.stringify && cached.parse) { // Only memoize the result if we have valid instance getJSON = function(){ return cached; }; return cached; } return null; }; /** * Applies properties from the source object to the target object.
* @param {Object} target The target of the properties. * @param {Object} source The source of the properties. * @param {Boolean} noOverwrite Set to True to only set non-existing properties. */ function apply(destination, source, noOverwrite){ var member; for (var prop in source) { if (source.hasOwnProperty(prop)) { if (prop in destination) { member = source[prop]; if (typeof member === "object") { apply(destination[prop], member, noOverwrite); } else if (!noOverwrite) { destination[prop] = source[prop]; } } else { destination[prop] = source[prop]; } } } return destination; } // This tests for the bug in IE where setting the [name] property using javascript causes the value to be redirected into [submitName]. function testForNamePropertyBug(){ var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input")); input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name]; document.body.removeChild(form); } /** * Creates a frame and appends it to the DOM. * @param config {object} This object can have the following properties * * @return The frames DOMElement * @type DOMElement */ function createFrame(config){ if (undef(HAS_NAME_PROPERTY_BUG)) { testForNamePropertyBug(); } var frame; // This is to work around the problems in IE6/7 with setting the name property. // Internally this is set as 'submitName' instead when using 'iframe.name = ...' // This is not required by easyXDM itself, but is to facilitate other use cases if (HAS_NAME_PROPERTY_BUG) { frame = document.createElement("