/** * This is the stock JSON2 implementation from www.json.org. * * Modifications include: * 1/ Removal of jslint settings * * @provides fb.thirdparty.json2 */ /* http://www.JSON.org/json2.js 2009-09-29 Public Domain. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See http://www.JSON.org/js.html This file creates a global JSON object containing two methods: stringify and parse. JSON.stringify(value, replacer, space) value any JavaScript value, usually an object or array. replacer an optional parameter that determines how object values are stringified for objects. It can be a function or an array of strings. space an optional parameter that specifies the indentation of nested structures. If it is omitted, the text will be packed without extra whitespace. If it is a number, it will specify the number of spaces to indent at each level. If it is a string (such as '\t' or ' '), it contains the characters used to indent at each level. This method produces a JSON text from a JavaScript value. When an object value is found, if the object contains a toJSON method, its toJSON method will be called and the result will be stringified. A toJSON method does not serialize: it returns the value represented by the name/value pair that should be serialized, or undefined if nothing should be serialized. The toJSON method will be passed the key associated with the value, and this will be bound to the value For example, this would serialize Dates as ISO strings. Date.prototype.toJSON = function (key) { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } return this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'; }; You can provide an optional replacer method. It will be passed the key and value of each member, with this bound to the containing object. The value that is returned from your method will be serialized. If your method returns undefined, then the member will be excluded from the serialization. If the replacer parameter is an array of strings, then it will be used to select the members to be serialized. It filters the results such that only members with keys listed in the replacer array are stringified. Values that do not have JSON representations, such as undefined or functions, will not be serialized. Such values in objects will be dropped; in arrays they will be replaced with null. You can use a replacer function to replace those with JSON values. JSON.stringify(undefined) returns undefined. The optional space parameter produces a stringification of the value that is filled with line breaks and indentation to make it easier to read. If the space parameter is a non-empty string, then that string will be used for indentation. If the space parameter is a number, then the indentation will be that many spaces. Example: text = JSON.stringify(['e', {pluribus: 'unum'}]); // text is '["e",{"pluribus":"unum"}]' text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' text = JSON.stringify([new Date()], function (key, value) { return this[key] instanceof Date ? 'Date(' + this[key] + ')' : value; }); // text is '["Date(---current time---)"]' JSON.parse(text, reviver) This method parses a JSON text to produce an object or array. It can throw a SyntaxError exception. The optional reviver parameter is a function that can filter and transform the results. It receives each of the keys and values, and its return value is used instead of the original value. If it returns what it received, then the structure is not modified. If it returns undefined then the member is deleted. Example: // Parse the text. Values that look like ISO date strings will // be converted to Date objects. myData = JSON.parse(text, function (key, value) { var a; if (typeof value === 'string') { a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); if (a) { return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])); } } return value; }); myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { var d; if (typeof value === 'string' && value.slice(0, 5) === 'Date(' && value.slice(-1) === ')') { d = new Date(value.slice(5, -1)); if (d) { return d; } } return value; }); This is a reference implementation. You are free to copy, modify, or redistribute. This code should be minified before deployment. See http://javascript.crockford.com/jsmin.html USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL. */ // Create a JSON object only if one does not already exist. We create the // methods in a closure to avoid creating global variables. if (!this.JSON) { this.JSON = {}; } (function () { function f(n) { // Format integers to have at least two digits. return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\' }, rep; function quote(string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { // Produce a string from holder[key]. var i, // The loop counter. k, // The member key. v, // The member value. length, mind = gap, partial, value = holder[key]; // If the value has a toJSON method, call it to obtain a replacement value. if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } // If we were called with a replacer function, then call the replacer to // obtain a replacement value. if (typeof rep === 'function') { value = rep.call(holder, key, value); } // What happens next depends on the value's type. switch (typeof value) { case 'string': return quote(value); case 'number': // JSON numbers must be finite. Encode non-finite numbers as null. return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': // If the value is a boolean or null, convert it to a string. Note: // typeof null does not produce 'null'. The case is included here in // the remote chance that this gets fixed someday. return String(value); // If the type is 'object', we might be dealing with an object or an array or // null. case 'object': // Due to a specification blunder in ECMAScript, typeof null is 'object', // so watch out for that case. if (!value) { return 'null'; } // Make an array to hold the partial results of stringifying this object value. gap += indent; partial = []; // Is the value an array? if (Object.prototype.toString.apply(value) === '[object Array]') { // The value is an array. Stringify every element. Use null as a placeholder // for non-JSON values. length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } // Join all of the elements together, separated with commas, and wrap them in // brackets. v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } // If the replacer is an array, use it to select the members to be stringified. if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { k = rep[i]; if (typeof k === 'string') { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { // Otherwise, iterate through all of the keys in the object. for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } // Join all of the member texts together, separated with commas, // and wrap them in braces. v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } // If the JSON object does not yet have a stringify method, give it one. if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { // The stringify method takes a value and an optional replacer, and an optional // space parameter, and returns a JSON text. The replacer can be a function // that can replace values, or an array of strings that will select the keys. // A default replacer method can be provided. Use of the space parameter can // produce text that is more easily readable. var i; gap = ''; indent = ''; // If the space parameter is a number, make an indent string containing that // many spaces. if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } // If the space parameter is a string, it will be used as the indent string. } else if (typeof space === 'string') { indent = space; } // If there is a replacer, it must be a function or an array. // Otherwise, throw an error. rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. return str('', {'': value}); }; } // If the JSON object does not yet have a parse method, give it one. if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { // The parse method takes a text and an optional reviver function, and returns // a JavaScript value if the text is a valid JSON text. var j; function walk(holder, key) { // The walk method is used to recursively walk the resulting structure so // that modifications can be made. var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } // Parsing happens in four stages. In the first stage, we replace certain // Unicode characters with escape sequences. JavaScript handles many characters // incorrectly, either silently deleting them, or treating them as line endings. cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } // In the second stage, we run the text against regular expressions that look // for non-JSON patterns. We are especially concerned with '()' and 'new' // because they can cause invocation, and '=' because it can cause mutation. // But just to be safe, we want to reject all unexpected forms. // We split the second stage into 4 regexp operations in order to work around // crippling inefficiencies in IE's and Safari's regexp engines. First we // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we // replace all simple value tokens with ']' characters. Third, we delete all // open brackets that follow a colon or comma or that begin the text. Finally, // we look to see that the remaining characters are only whitespace or ']' or // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. if (/^[\],:{}\s]*$/. test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { // In the third stage we use the eval function to compile the text into a // JavaScript structure. The '{' operator is subject to a syntactic ambiguity // in JavaScript: it can begin a block or an object literal. We wrap the text // in parens to eliminate the ambiguity. j = eval('(' + text + ')'); // In the optional fourth stage, we recursively walk the new structure, passing // each name/value pair to a reviver function for possible transformation. return typeof reviver === 'function' ? walk({'': j}, '') : j; } // If the text is not JSON parseable, then a SyntaxError is thrown. throw new SyntaxError('JSON.parse'); }; } }()); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.prelude */ /** * Prelude. * * Namespaces are one honking great idea -- let's do more of those! * -- Tim Peters * * The Prelude is what keeps us from being messy. In order to co-exist with * arbitary environments, we need to control our footprint. The one and only * rule to follow here is that we need to limit the globals we introduce. The * only global we should every have is ``FB``. This is exactly what the prelude * enables us to do. * * The main method to take away from this file is `FB.copy()`_. As the name * suggests it copies things. Its powerful -- but to get started you only need * to know that this is what you use when you are augmenting the FB object. For * example, this is skeleton for how ``FB.Event`` is defined:: * * FB.provide('Event', { * subscribe: function() { ... }, * unsubscribe: function() { ... }, * fire: function() { ... } * }); * * This is similar to saying:: * * FB.Event = { * subscribe: function() { ... }, * unsubscribe: function() { ... }, * fire: function() { ... } * }; * * Except it does some housekeeping, prevents redefinition by default and other * goodness. * * .. _FB.copy(): #method_FB.copy * * @class FB * @static * @access private */ if (!window.FB) { FB = { // use the init method to set these values correctly _apiKey : null, _session : null, _userStatus : 'unknown', // or 'notConnected' or 'connected' // logging is enabled by default. this is the logging shown to the // developer and not at all noisy. _logging: true, _inCanvas: ( (window.location.search.indexOf('fb_sig_in_iframe=1') > -1) || (window.location.search.indexOf('session=') > -1)), // // DYNAMIC DATA // // the various domains needed for using Connect _domain: { api : 'https://api.facebook.com/', api_read : 'https://api-read.facebook.com/', cdn : (window.location.protocol == 'https:' ? 'https://s-static.ak.fbcdn.net/' : 'http://static.ak.fbcdn.net/'), graph : 'https://graph.facebook.com/', staticfb : 'http://static.ak.facebook.com/', www : window.location.protocol + '//www.facebook.com/' }, _locale: null, _localeIsRtl: false, /** * Copies things from source into target. * * @access private * @param target {Object} the target object where things will be copied * into * @param source {Object} the source object where things will be copied * from * @param overwrite {Boolean} indicate if existing items should be * overwritten * @param tranform {function} [Optional], transformation function for * each item */ copy: function(target, source, overwrite, transform) { for (var key in source) { if (overwrite || typeof target[key] === 'undefined') { target[key] = transform ? transform(source[key]) : source[key]; } } return target; }, /** * Create a namespaced object. * * @access private * @param name {String} full qualified name ('Util.foo', etc.) * @param value {Object} value to set. Default value is {}. [Optional] * @return {Object} The created object */ create: function(name, value) { var node = window.FB, // We will use 'FB' as root namespace nameParts = name ? name.split('.') : [], c = nameParts.length; for (var i = 0; i < c; i++) { var part = nameParts[i]; var nso = node[part]; if (!nso) { nso = (value && i + 1 == c) ? value : {}; node[part] = nso; } node = nso; } return node; }, /** * Copy stuff from one object to the specified namespace that * is FB.. * If the namespace target doesn't exist, it will be created automatically. * * @access private * @param target {Object|String} the target object to copy into * @param source {Object} the source object to copy from * @param overwrite {Boolean} indicate if we should overwrite * @return {Object} the *same* target object back */ provide: function(target, source, overwrite) { // a string means a dot separated object that gets appended to, or created return FB.copy( typeof target == 'string' ? FB.create(target) : target, source, overwrite ); }, /** * Generates a weak random ID. * * @access private * @return {String} a random ID */ guid: function() { return 'f' + (Math.random() * (1<<30)).toString(16).replace('.', ''); }, /** * Logs a message for the developer if logging is on. * * @access private * @param args {Object} the thing to log */ log: function(args) { if (FB._logging) { //TODO what is window.Debug, and should it instead be relying on the // event fired below? //#JSCOVERAGE_IF 0 if (window.Debug && window.Debug.writeln) { window.Debug.writeln(args); } else if (window.console) { window.console.log(args); } //#JSCOVERAGE_ENDIF } // fire an event if the event system is available if (FB.Event) { FB.Event.fire('fb.log', args); } }, /** * Shortcut for document.getElementById * @method $ * @param {string} DOM id * @return DOMElement * @access private */ $: function(id) { return document.getElementById(id); } }; } /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.type * @layer basic * @requires fb.prelude */ // Provide Class/Type support. // TODO: As a temporary hack, this docblock is written as if it describes the // top level FB namespace. This is necessary because the current documentation // parser uses the description from this file for some reason. /** * The top level namespace exposed by the SDK. Look at the [readme on * **GitHub**][readme] for more information. * * [readme]: http://github.com/facebook/connect-js * * @class FB * @static */ FB.provide('', { /** * Bind a function to a given context and arguments. * * @static * @access private * @param fn {Function} the function to bind * @param context {Object} object used as context for function execution * @param {...} arguments additional arguments to be bound to the function * @returns {Function} the bound function */ bind: function() { var args = Array.prototype.slice.call(arguments), fn = args.shift(), context = args.shift(); return function() { return fn.apply( context, args.concat(Array.prototype.slice.call(arguments)) ); }; }, /** * Create a new class. * * Note: I have to use 'Class' instead of 'class' because 'class' is * a reserved (but unused) keyword. * * @access private * @param name {string} class name * @param constructor {function} class constructor * @param proto {object} instance methods for class */ Class: function(name, constructor, proto) { if (FB.CLASSES[name]) { return FB.CLASSES[name]; } var newClass = constructor || function() {}; newClass.prototype = proto; newClass.prototype.bind = function(fn) { return FB.bind(fn, this); }; newClass.prototype.constructor = newClass; FB.create(name, newClass); FB.CLASSES[name] = newClass; return newClass; }, /** * Create a subclass * * Note: To call base class constructor, use this._base(...). * If you override a method 'foo' but still want to call * the base class's method 'foo', use this._callBase('foo', ...) * * @access private * @param {string} name class name * @param {string} baseName, * @param {function} constructor class constructor * @param {object} proto instance methods for class */ subclass: function(name, baseName, constructor, proto) { if (FB.CLASSES[name]) { return FB.CLASSES[name]; } var base = FB.create(baseName); FB.copy(proto, base.prototype); proto._base = base; proto._callBase = function(method) { var args = Array.prototype.slice.call(arguments, 1); return base.prototype[method].apply(this, args); }; return FB.Class( name, constructor ? constructor : function() { if (base.apply) { base.apply(this, arguments); } }, proto ); }, CLASSES: {} }); /** * @class FB.Type * @static * @private */ FB.provide('Type', { isType: function(obj, type) { while (obj) { if (obj.constructor === type || obj === type) { return true; } else { obj = obj._base; } } return false; } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * Contains the public method ``FB.api`` and the internal implementation * ``FB.ApiServer``. * * @provides fb.api * @requires fb.prelude * fb.qs * fb.flash * fb.json */ /** * API calls. * * @class FB * @static * @access private */ FB.provide('', { /** * Make a API call to the [Graph API](/docs/api). * * Server-side calls are available via the JavaScript SDK that allow you to * build rich applications that can make API calls against the Facebook * servers directly from the user's browser. This can improve performance in * many scenarios, as compared to making all calls from your server. It can * also help reduce, or eliminate the need to proxy the requests thru your * own servers, freeing them to do other things. * * The range of APIs available covers virtually all facets of Facebook. * Public data such as [names][names] and [profile pictures][profilepic] are * available if you know the id of the user or object. Various parts of the * API are available depending on the [connect status and the * permissions](FB.login) the user has granted your application. * * Except the path, all arguments to this function are optional. * * Get the **f8 Page Object**: * * FB.api('/f8', function(response) { * alert(response.company_overview); * }); * * If you have an [authenticated user](FB.login), get their **User Object**: * * FB.api('/me', function(response) { * alert(response.name); * }); * * Get the 3 most recent **Post Objects** *Connected* to (in other words, * authored by) the *f8 Page Object*: * * FB.api('/f8/posts', { limit: 3 }, function(response) { * for (var i=0, l=response.length; i -1 ? '&' : '?') + FB.QS.encode(params) ); if (url.length > 2000) { throw new Error('JSONP only support a maximum of 2000 bytes of input.'); } // this is the JSONP callback invoked by the response FB.ApiServer._callbacks[g] = function(response) { cb && cb(response); delete FB.ApiServer._callbacks[g]; script.parentNode.removeChild(script); }; script.src = url; document.getElementsByTagName('head')[0].appendChild(script); }, /** * Flash based HTTP Client. * * @access private * @param domain {String} the domain key, one of 'api' or 'graph' * @param path {String} the request path * @param method {String} the http method * @param params {Object} the parameters for the query * @param cb {Function} the callback function to handle the response */ flash: function(domain, path, method, params, cb) { if (!window.FB_OnXdHttpResult) { // the SWF calls this global function when a HTTP response is available // FIXME: remove global window.FB_OnXdHttpResult = function(reqId, data) { FB.ApiServer._callbacks[reqId](decodeURIComponent(data)); }; } FB.Flash.onReady(function() { var url = FB._domain[domain] + path, body = FB.QS.encode(params); if (method === 'get') { // convert GET to POST if needed based on URL length if (url.length + body.length > 2000) { if (domain === 'graph') { params.method = 'get'; } method = 'post'; body = FB.QS.encode(params); } else { url += (url.indexOf('?') > -1 ? '&' : '?') + body; body = ''; } } else if (method !== 'post') { // we use method override and do a POST for PUT/DELETE as flash has // trouble otherwise if (domain === 'graph') { params.method = method; } method = 'post'; body = FB.QS.encode(params); } // fire the request var reqId = document.XdComm.sendXdHttpRequest( method.toUpperCase(), url, body, null); // callback FB.ApiServer._callbacks[reqId] = function(response) { cb && cb(FB.JSON.parse(response)); delete FB.ApiServer._callbacks[reqId]; }; }); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.auth * @requires fb.prelude * fb.qs * fb.event * fb.json * fb.ui */ /** * Authentication, Authorization & Sessions. * * @class FB * @static * @access private */ FB.provide('', { /** * Find out the current status from the server, and get a session if the user * is connected. * * The user's status or the question of *who is the current user* is * the first thing you will typically start with. For the answer, we * ask facebook.com. Facebook will answer this question in one of * two ways: * * 1. Someone you don't know. * 2. Someone you know and have interacted with. Here's a session for them. * * Here's how you find out: * * FB.getLoginStatus(function(response) { * if (response.session) { * // logged in and connected user, someone you know * } else { * // no user session available, someone you dont know * } * }); * * The example above will result in the callback being invoked **once** * on load based on the session from www.facebook.com. JavaScript applications * are typically written with heavy use of events, and the SDK **encourages** * this by exposing various events. These are fired by the various * interactions with authentication flows, such as [FB.login()][login] or * [[wiki:fb:login-button]]. Widgets such as [[wiki:fb:comments (XFBML)]] * may also trigger authentication. * * **Events** * * #### auth.login * This event is fired when your application first notices the user (in other * words, gets a session when it didn't already have a valid one). * #### auth.logout * This event is fired when your application notices that there is no longer * a valid user (in other words, it had a session but can no longer validate * the current user). * #### auth.sessionChange * This event is fired for **any** auth related change as they all affect the * session: login, logout, session refresh. Sessions are refreshed over time * as long as the user is active with your application. * #### auth.statusChange * Typically you will want to use the auth.sessionChange event. But in rare * cases, you want to distinguish between these three states: * * - Connected * - Logged into Facebook but not connected with your application * - Not logged into Facebook at all. * * The [FB.Event.subscribe][subscribe] and * [FB.Event.unsubscribe][unsubscribe] functions are used to subscribe to * these events. For example: * * FB.Event.subscribe('auth.login', function(response) { * // do something with response * }); * * The response object returned to all these events is the same as the * response from [FB.getLoginStatus][getLoginStatus], [FB.login][login] or * [FB.logout][logout]. This response object contains: * * status * : The status of the User. One of `connected`, `notConnected` or `unknown`. * * session * : The session object. * * perms * : The comma separated permissions string. This is specific to a * permissions call. It is not persistent. * * [subscribe]: /docs/reference/javascript/FB.Event.subscribe * [unsubscribe]: /docs/reference/javascript/FB.Event.unsubscribe * [getLoginStatus]: /docs/reference/javascript/FB.getLoginStatus * [login]: /docs/reference/javascript/FB.login * [logout]: /docs/reference/javascript/FB.logout * * @access public * @param cb {Function} The callback function. * @param force {Boolean} Force reloading the login status (default `false`). */ getLoginStatus: function(cb, force) { if (!FB._apiKey) { FB.log('FB.getLoginStatus() called before calling FB.init().'); return; } // we either invoke the callback right away if the status has already been // loaded, or queue it up for when the load is done. if (cb) { if (!force && FB.Auth._loadState == 'loaded') { cb({ status: FB._userStatus, session: FB._session }); return; } else { FB.Event.subscribe('FB.loginStatus', cb); } } // if we're already loading, and this is not a force load, we're done if (!force && FB.Auth._loadState == 'loading') { return; } FB.Auth._loadState = 'loading'; // invoke the queued sessionLoad callbacks var lsCb = function(response) { // done FB.Auth._loadState = 'loaded'; // invoke callbacks FB.Event.fire('FB.loginStatus', response); FB.Event.clear('FB.loginStatus'); }; // finally make the call to login status FB.ui({ method: 'auth.status', display: 'hidden' }, lsCb); }, /** * *Synchronous* accessor for the current Session. The **synchronous** * nature of this method is what sets it apart from the other login methods. * It is similar in nature to [FB.getLoginStatus()][FB.getLoginStatus], but * it just **returns** the session. Many parts of your application already * *assume* the user is connected with your application. In such cases, you * may want to avoid the overhead of making asynchronous calls. * * NOTE: You should never use this method at *page load* time. Generally, it * is safer to use [FB.getLoginStatus()][FB.getLoginStatus] if you are * unsure. * * [FB.getLoginStatus]: /docs/reference/javascript/FB.getLoginStatus * * @access public * @return {Object} the current Session if available, `null` otherwise */ getSession: function() { return FB._session; }, /** * Login/Authorize/Permissions. * * Once you have determined the user's status, you may need to * prompt the user to login. It is best to delay this action to * reduce user friction when they first arrive at your site. You can * then prompt and show them the "Connect with Facebook" button * bound to an event handler which does the following: * * FB.login(function(response) { * if (response.session) { * // user successfully logged in * } else { * // user cancelled login * } * }); * * You should **only** call this on a user event as it opens a * popup. Most browsers block popups, _unless_ they were initiated * from a user event, such as a click on a button or a link. * * * Depending on your application's needs, you may need additional * permissions from the user. A large number of calls do not require * any additional permissions, so you should first make sure you * need a permission. This is a good idea because this step * potentially adds friction to the user's process. Another point to * remember is that this call can be made even _after_ the user has * first connected. So you may want to delay asking for permissions * until as late as possible: * * FB.login(function(response) { * if (response.session) { * if (response.perms) { * // user is logged in and granted some permissions. * // perms is a comma separated list of granted permissions * } else { * // user is logged in, but did not grant any permissions * } * } else { * // user is not logged in * } * }, {perms:'read_stream,publish_stream,offline_access'}); * * @access public * @param cb {Function} The callback function. * @param opts {Object} (_optional_) Options to modify login behavior. * * Name | Type | Description * ------------------------ | ------- | -------------------------------------------------------------------------------- * perms | String | Comma separated list of [Extended permissions](/docs/authentication/permissions) * enable_profile_selector | Boolean | When true, prompt the user to grant permission for one or more Pages. * profile_selector_ids | String | Comma separated list of IDs to display in the profile selector. */ login: function(cb, opts) { opts = FB.copy({ method: 'auth.login', display: 'popup' }, opts || {}); FB.ui(opts, cb); }, /** * Logout the user in the background. * * Just like logging in is tied to facebook.com, so is logging out -- and * this call logs the user out of both Facebook and your site. This is a * simple call: * * FB.logout(function(response) { * // user is now logged out * }); * * NOTE: You can only log out a user that is connected to your site. * * @access public * @param cb {Function} The callback function. */ logout: function(cb) { FB.ui({ method: 'auth.logout', display: 'hidden' }, cb); } }); /** * Internal Authentication implementation. * * @class FB.Auth * @static * @access private */ FB.provide('Auth', { // pending callbacks for FB.getLoginStatus() calls _callbacks: [], /** * Set a new session value. Invokes all the registered subscribers * if needed. * * @access private * @param session {Object} the new Session * @param status {String} the new status * @return {Object} the "response" object */ setSession: function(session, status) { // detect special changes before changing the internal session var login = !FB._session && session, logout = FB._session && !session, both = FB._session && session && FB._session.uid != session.uid, sessionChange = login || logout || (FB._session && session && FB._session.session_key != session.session_key), statusChange = status != FB._userStatus; var response = { session : session, status : status }; FB._session = session; FB._userStatus = status; // If cookie support is enabled, set the cookie. Cookie support does not // rely on events, because we want the cookie to be set _before_ any of the // event handlers are fired. Note, this is a _weak_ dependency on Cookie. if (sessionChange && FB.Cookie && FB.Cookie.getEnabled()) { FB.Cookie.set(session); } // events if (statusChange) { /** * Fired when the status changes. * * @event auth.statusChange */ FB.Event.fire('auth.statusChange', response); } if (logout || both) { /** * Fired when a logout action is performed. * * @event auth.logout */ FB.Event.fire('auth.logout', response); } if (login || both) { /** * Fired when a login action is performed. * * @event auth.login */ FB.Event.fire('auth.login', response); } if (sessionChange) { /** * Fired when the session changes. This includes a session being * refreshed, or a login or logout action. * * @event auth.sessionChange */ FB.Event.fire('auth.sessionChange', response); } // re-setup a timer to refresh the session if needed. we only do this if // FB.Auth._loadState exists, indicating that the application relies on the // JS to get and refresh session information (vs managing it themselves). if (FB.Auth._refreshTimer) { window.clearTimeout(FB.Auth._refreshTimer); delete FB.Auth._refreshTimer; } if (FB.Auth._loadState && session && session.expires) { // refresh every 20 minutes. we don't rely on the expires time because // then we would also need to rely on the local time available in JS // which is often incorrect. FB.Auth._refreshTimer = window.setTimeout(function() { FB.getLoginStatus(null, true); // force refresh }, 1200000); // 20 minutes } return response; }, /** * This handles receiving a session from: * - login_status.php * - login.php * - tos.php * * It also (optionally) handles the ``xxRESULTTOKENxx`` response from: * - prompt_permissions.php * * And calls the given callback with:: * * { * session: session or null, * status: 'unknown' or 'notConnected' or 'connected', * perms: comma separated string of perm names * } * * @access private * @param cb {Function} the callback function * @param frame {String} the frame id for the callback is tied to * @param target {String} parent or opener to indicate window relation * @param isDefault {Boolean} is this the default callback for the frame * @param status {String} the connect status this handler will trigger * @param session {Object} backup session, if none is found in response * @return {String} the xd url bound to the callback */ xdHandler: function(cb, frame, target, isDefault, status, session) { return FB.UIServer._xdNextHandler(function(params) { try { session = FB.JSON.parse(params.session); } catch (x) { // ignore parse errors } var response = FB.Auth.setSession(session || null, status); // incase we were granted some new permissions response.perms = ( params.result != 'xxRESULTTOKENxx' && params.result || ''); // user defined callback cb && cb(response); }, frame, target, isDefault) + '&result=xxRESULTTOKENxx'; } }); FB.provide('UIServer.Methods', { 'auth.login': { size : { width: 627, height: 326 }, url : 'login.php', transform : function(call) { //FIXME if (!FB._apiKey) { FB.log('FB.login() called before calling FB.init().'); return; } // if we already have a session and permissions are not being requested, // we just fire the callback if (FB._session && !call.params.perms) { FB.log('FB.login() called when user is already connected.'); call.cb && call.cb({ status: FB._userStatus, session: FB._session }); return; } var xdHandler = FB.Auth.xdHandler, cb = call.cb, id = call.id, session = FB._session, cancel = xdHandler( cb, id, 'opener', true, // isDefault FB._userStatus, session), next = xdHandler( cb, id, 'opener', false, // isDefault 'connected', session); FB.copy(call.params, { cancel_url : cancel, channel_url : window.location.toString(), next : next, fbconnect : FB._inCanvas ? 0 : 1, req_perms : call.params.perms, enable_profile_selector : call.params.enable_profile_selector, profile_selector_ids : call.params.profile_selector_ids, return_session : 1, session_version : 3, v : '1.0' }); delete call.cb; delete call.params.perms; //TODO fix name to be the same on server return call; } }, 'auth.logout': { url : 'logout.php', transform : function(call) { //FIXME make generic if (!FB._apiKey) { FB.log('FB.logout() called before calling FB.init().'); } else if (!FB._session) { FB.log('FB.logout() called without a session.'); } else { call.params.next = FB.Auth.xdHandler( call.cb, call.id, 'parent', false, 'unknown'); return call; } } }, 'auth.status': { url : 'extern/login_status.php', transform : function(call) { var cb = call.cb, id = call.id, xdHandler = FB.Auth.xdHandler; delete call.cb; FB.copy(call.params, { no_session : xdHandler(cb, id, 'parent', false, 'notConnected'), no_user : xdHandler(cb, id, 'parent', false, 'unknown'), ok_session : xdHandler(cb, id, 'parent', false, 'connected'), session_version : 3, extern: FB._inCanvas ? 0 : 2 }); return call; } } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.canvas * @requires fb.prelude * fb.array * fb.content * fb.qs */ /** * Things used by Canvas apps. * * --------------------------------------------------------------------- * IMPORTANT NOTE: IF YOU ARE USING THESE FUNCTIONS, MAKE SURE YOU GO TO * * http://www.facebook.com/developers * * CLICK YOUR APP, CLICK EDIT SETTINGS, CLICK MIGRATIONS AND ENABLE * * New SDKs * --------------------------------------------------------------------- * * @class FB.Canvas * @static * @access private */ FB.provide('Canvas', { /** * The timer. We keep it around so we can shut if off */ _timer: null, /** * Tells Facebook to resize your iframe. * * ## Migration Requirement * * To use this function, you MUST have enabled the *New SDKs* * [migration](http://developers.facebook.com/blog/post/363). * * ## Examples * * Call this whenever you need a resize. This usually means, once after * pageload, and whenever your content size changes. * * window.fbAsyncInit = function() { * FB.Canvas.setSize(); * } * * // Do things that will sometimes call sizeChangeCallback() * * function sizeChangeCallback() { * FB.Canvas.setSize(); * } * * It will default to the current size of the frame, but if you have a need * to pick your own size, you can use the params array. * * FB.Canvas.setSize({ width: 640, height: 480 }); // Live in the past * * The max width is whatever you picked in your app settings, and there is no * max height. * * @param {Object} params * * Property | Type | Description | Argument | Default * -------- | ------- | -------------------------------- | ---------- | ------- * width | Integer | Desired width. Max is app width. | *Optional* | frame width * height | Integer | Desired height. | *Optional* | frame height * * @author ptarjan */ setSize: function(params) { // setInterval calls its function with an integer if (typeof params != "object") { params = {}; } params = FB.copy(params || {}, FB.Canvas._computeContentSize()); // Deep compare if (FB.Canvas._lastSize && FB.Canvas._lastSize.width == params.width && FB.Canvas._lastSize.height == params.height) { return false; } FB.Canvas._lastSize = params; FB.Canvas._sendMessageToFacebook({ method: 'setSize', params: params }); return true; }, /** * Starts or stops a timer which resizes your iframe every few milliseconds. * * Used to be known as: * [startTimerToSizeToContent](http://wiki.developers.facebook.com/index.php/Resizable_IFrame) * * ## Migration Requirement * * To use this function, you MUST have enabled the *New SDKs* * [migration](http://developers.facebook.com/blog/post/363). * * ## Examples * * This function is useful if you know your content will change size, but you * don't know when. There will be a slight delay, so if you know when your * content changes size, you should call [setSize](FB.Canvas.setSize) * yourself (and save your user's CPU cycles). * * window.fbAsyncInit = function() { * FB.Canvas.setAutoResize(); * } * * If you ever need to stop the timer, just pass false. * * FB.Canvas.setAutoResize(false); * * If you want the timer to run at a different interval, you can do that too. * * FB.Canvas.setAutoResize(91); // Paul's favourite number * * Note: If there is only 1 parameter and it is a number, it is assumed to be * the interval. * * @param {Boolean} onOrOff Whether to turn the timer on or off. truthy == * on, falsy == off. **default** is true * @param {Integer} interval How often to resize (in ms). **default** is * 100ms * * @author ptarjan */ setAutoResize: function(onOrOff, interval) { // I did this a few times, so I expect many users will too if (interval === undefined && typeof onOrOff == "number") { interval = onOrOff; onOrOff = true; } if (onOrOff === undefined || onOrOff) { if (FB.Canvas._timer === null) { FB.Canvas._timer = window.setInterval(FB.Canvas.setSize, interval || 100); // 100 ms is the default } FB.Canvas.setSize(); } else { if (FB.Canvas._timer !== null) { window.clearInterval(FB.Canvas._timer); FB.Canvas._timer = null; } } }, /** * Determine the size of the actual contents of the iframe. * * This is the same number jQuery seems to give for * $(document).height() but still causes a scrollbar in some browsers * on some sites. * Patches and test cases are welcome. */ _computeContentSize: function() { var body = document.body, docElement = document.documentElement, right = 0, bottom = Math.max( Math.max(body.offsetHeight, body.scrollHeight) + body.offsetTop, Math.max(docElement.offsetHeight, docElement.scrollHeight) + docElement.offsetTop); if (body.offsetWidth < body.scrollWidth) { right = body.scrollWidth + body.offsetLeft; } else { FB.Array.forEach(body.childNodes, function(child) { var childRight = child.offsetWidth + child.offsetLeft; if (childRight > right) { right = childRight; } }); } if (docElement.clientLeft > 0) { right += (docElement.clientLeft * 2); } if (docElement.clientTop > 0) { bottom += (docElement.clientTop * 2); } return {height: bottom, width: right}; }, /** * Sends a request back to facebook. * * @author ptarjan */ _sendMessageToFacebook: function(message) { var url = FB._domain.staticfb + 'connect/canvas_proxy.php#' + FB.QS.encode({method: message.method, params: FB.JSON.stringify(message.params)}); var root = FB.Content.appendHidden(''); FB.Content.insertIframe({ url: url, root: root, width: 1, height: 1, onload: function() { setTimeout(function() { root.parentNode.removeChild(root); }, 10); } }); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.content * @requires fb.prelude fb.array */ /** * "Content" is a very flexible term. Helpers for things like hidden * DOM content, iframes and popups. * * @class FB.Content * @static * @access private */ FB.provide('Content', { _root : null, _hiddenRoot : null, _callbacks : {}, /** * Append some content. * * @access private * @param content {String|Node} a DOM Node or HTML string * @param root {Node} (optional) a custom root node * @return {Node} the node that was just appended */ append: function(content, root) { // setup the root node, creating it if necessary if (!root) { if (!FB.Content._root) { FB.Content._root = root = FB.$('fb-root'); if (!root) { FB.log('The "fb-root" div has not been created.'); return; } else { root.className += ' fb_reset'; } } else { root = FB.Content._root; } } if (typeof content == 'string') { var div = document.createElement('div'); root.appendChild(div).innerHTML = content; return div; } else { return root.appendChild(content); } }, /** * Append some hidden content. * * @access private * @param content {String|Node} a DOM Node or HTML string * @return {Node} the node that was just appended */ appendHidden: function(content) { if (!FB.Content._hiddenRoot) { var hiddenRoot = document.createElement('div'), style = hiddenRoot.style; style.position = 'absolute'; style.top = '-10000px'; style.width = style.height = 0; FB.Content._hiddenRoot = FB.Content.append(hiddenRoot); } return FB.Content.append(content, FB.Content._hiddenRoot); }, /** * Insert a new iframe. Unfortunately, its tricker than you imagine. * * NOTE: These iframes have no border, overflow hidden and no scrollbars. * * The opts can contain: * root DOMElement required root node (must be empty) * url String required iframe src attribute * className String optional class attribute * height Integer optional height in px * id String optional id attribute * name String optional name attribute * onload Function optional onload handler * width Integer optional width in px * * @access private * @param opts {Object} the options described above */ insertIframe: function(opts) { // // Browsers evolved. Evolution is messy. // opts.id = opts.id || FB.guid(); opts.name = opts.name || FB.guid(); // Dear IE, screw you. Only works with the magical incantations. // Dear FF, screw you too. Needs src _after_ DOM insertion. // Dear Webkit, you're okay. Works either way. var guid = FB.guid(), // Since we set the src _after_ inserting the iframe node into the DOM, // some browsers will fire two onload events, once for the first empty // iframe insertion and then again when we set the src. Here some // browsers are Webkit browsers which seem to be trying to do the // "right thing". So we toggle this boolean right before we expect the // correct onload handler to get fired. srcSet = false, onloadDone = false; FB.Content._callbacks[guid] = function() { if (srcSet && !onloadDone) { onloadDone = true; opts.onload && opts.onload(opts.root.firstChild); } }; //#JSCOVERAGE_IF if (document.attachEvent) { var html = ( '' ); // There is an IE bug with iframe caching that we have to work around. We // need to load a dummy iframe to consume the initial cache stream. The // setTimeout actually sets the content to the HTML we created above, and // because its the second load, we no longer suffer from cache sickness. // It must be javascript:false instead of about:blank, otherwise IE6 will // complain in https. // Since javascript:false actually result in an iframe containing the // string 'false', we set the iframe height to 1px so that it gets loaded // but stays invisible. opts.root.innerHTML = ''; // Now we'll be setting the real src. srcSet = true; // You may wonder why this is a setTimeout. Read the IE source if you can // somehow get your hands on it, and tell me if you figure it out. This // is a continuation of the above trick which apparently does not work if // the innerHTML is changed right away. We need to break apart the two // with this setTimeout 0 which seems to fix the issue. window.setTimeout(function() { opts.root.innerHTML = html; }, 0); } else { // This block works for all non IE browsers. But it's specifically // designed for FF where we need to set the src after inserting the // iframe node into the DOM to prevent cache issues. var node = document.createElement('iframe'); node.id = opts.id; node.name = opts.name; node.onload = FB.Content._callbacks[guid]; node.style.border = 'none'; node.style.overflow = 'hidden'; if (opts.className) { node.className = opts.className; } if (opts.height) { node.style.height = opts.height + 'px'; } if (opts.width) { node.style.width = opts.width + 'px'; } opts.root.appendChild(node); // Now we'll be setting the real src. srcSet = true; node.src = opts.url; } }, /** * Dynamically generate a
and POST it to the given target. * * The opts MUST contain: * url String action URL for the form * target String the target for the form * params Object the key/values to be used as POST input * * @access protected * @param opts {Object} the options */ postTarget: function(opts) { var form = document.createElement('form'); form.action = opts.url; form.target = opts.target; form.method = 'POST'; FB.Content.appendHidden(form); FB.Array.forEach(opts.params, function(val, key) { if (val !== null && val !== undefined) { var input = document.createElement('input'); input.name = key; input.value = val; form.appendChild(input); } }); form.submit(); form.parentNode.removeChild(form); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.cookie * @requires fb.prelude * fb.qs * fb.event */ /** * Cookie Support. * * @class FB.Cookie * @static * @access private */ FB.provide('Cookie', { /** * Holds the base_domain property to match the Cookie domain. * * @access private * @type String */ _domain: null, /** * Indicate if Cookie support should be enabled. * * @access private * @type Boolean */ _enabled: false, /** * Enable or disable Cookie support. * * @access private * @param val {Boolean} true to enable, false to disable */ setEnabled: function(val) { FB.Cookie._enabled = val; }, /** * Return the current status of the cookie system. * * @access private * @returns {Boolean} true if Cookie support is enabled */ getEnabled: function() { return FB.Cookie._enabled; }, /** * Try loading the session from the Cookie. * * @access private * @return {Object} the session object from the cookie if one is found */ load: function() { var // note, we have the opening quote for the value in the regex, but do // not have a closing quote. this is because the \b already handles it. cookie = document.cookie.match('\\bfbs_' + FB._apiKey + '="([^;]*)\\b'), session; if (cookie) { // url encoded session stored as "sub-cookies" session = FB.QS.decode(cookie[1]); // decodes as a string, convert to a number session.expires = parseInt(session.expires, 10); // capture base_domain for use when we need to clear FB.Cookie._domain = session.base_domain; } return session; }, /** * Helper function to set cookie value. * * @access private * @param val {String} the string value (should already be encoded) * @param ts {Number} a unix timestamp denoting expiry * @param domain {String} optional domain for cookie */ setRaw: function(val, ts, domain) { document.cookie = 'fbs_' + FB._apiKey + '="' + val + '"' + (val && ts == 0 ? '' : '; expires=' + new Date(ts * 1000).toGMTString()) + '; path=/' + (domain ? '; domain=.' + domain : ''); // capture domain for use when we need to clear FB.Cookie._domain = domain; }, /** * Set the cookie using the given session object. * * @access private * @param session {Object} the session object */ set: function(session) { session ? FB.Cookie.setRaw( FB.QS.encode(session), session.expires, session.base_domain) : FB.Cookie.clear(); }, /** * Clear the cookie. * * @access private */ clear: function() { FB.Cookie.setRaw('', 0, FB.Cookie._domain); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.dialog * @requires fb.prelude * fb.intl * fb.array * fb.content * fb.dom * fb.css.dialog */ /** * Dialog creation and management. * * @class FB.Dialog * @static * @private */ FB.provide('Dialog', { /** * The loader element. * * @access private * @type DOMElement */ _loaderEl: null, /** * The stack of active dialogs. * * @access private * @type Array */ _stack: [], /** * The currently visible dialog. * * @access private * @type DOMElement */ _active: null, /** * Find the root dialog node for a given element. This will walk up the DOM * tree and while a node exists it will check to see if has the fb_dialog * class and if it does returns it. * * @access private * @param node {DOMElement} a child node of the dialog * @return {DOMElement} the root dialog element if found */ _findRoot: function(node) { while (node) { if (FB.Dom.containsCss(node, 'fb_dialog')) { return node; } node = node.parentNode; } }, /** * Show the "Loading..." dialog. This is a special dialog which does not * follow the standard stacking semantics. If a callback is provided, a * cancel action is provided using the "X" icon. * * @access private * @param cb {Function} optional callback for the "X" action */ _showLoader: function(cb) { if (!FB.Dialog._loaderEl) { FB.Dialog._loaderEl = FB.Dialog._findRoot(FB.Dialog.create({ content: ( '
' + FB.Intl.tx('sh:loading') + '' + '
' ) })); } // this needs to be done for each invocation of _showLoader. since we don't // stack loaders and instead simply hold on to the last one, it is possible // that we are showing nothing when we can potentially be showing the // loading dialog for a previously activated but not yet loaded dialog. var loaderClose = FB.$('fb_dialog_loader_close'); if (cb) { FB.Dom.removeCss(loaderClose, 'fb_hidden'); loaderClose.onclick = function() { FB.Dialog._hideLoader(); cb(); }; } else { FB.Dom.addCss(loaderClose, 'fb_hidden'); loaderClose.onclick = null; } FB.Dialog._makeActive(FB.Dialog._loaderEl); }, /** * Hide the loading dialog if one is being shown. * * @access private */ _hideLoader: function() { if (FB.Dialog._loaderEl && FB.Dialog._loaderEl == FB.Dialog._active) { FB.Dialog._loaderEl.style.top = '-10000px'; } }, /** * Center a dialog based on its current dimensions and place it in the * visible viewport area. * * @access private * @param el {DOMElement} the dialog node */ _makeActive: function(el) { FB.Dialog._lowerActive(); var dialog = { width : parseInt(el.offsetWidth, 10), height : parseInt(el.offsetHeight, 10) }, view = FB.Dom.getViewportInfo(), left = (view.scrollLeft + (view.width - dialog.width) / 2), top = (view.scrollTop + (view.height - dialog.height) / 2.5); el.style.left = (left > 0 ? left : 0) + 'px'; el.style.top = (top > 0 ? top : 0) + 'px'; FB.Dialog._active = el; }, /** * Lower the current active dialog if there is one. * * @access private * @param node {DOMElement} the dialog node */ _lowerActive: function() { if (!FB.Dialog._active) { return; } FB.Dialog._active.style.top = '-10000px'; FB.Dialog._active = null; }, /** * Remove the dialog from the stack. * * @access private * @param node {DOMElement} the dialog node */ _removeStacked: function(dialog) { FB.Dialog._stack = FB.Array.filter(FB.Dialog._stack, function(node) { return node != dialog; }); }, /** * Create a dialog. Returns the node of the dialog within which the caller * can inject markup. Optional HTML string or a DOMElement can be passed in * to be set as the content. Note, the dialog is hidden by default. * * @access protected * @param opts {Object} Options: * Property | Type | Description | Default * --------- | ----------------- | --------------------------------- | ------- * content | String|DOMElement | HTML String or DOMElement | * loader | Boolean | `true` to show the loader dialog | `false` * onClose | Boolean | callback if closed | * closeIcon | Boolean | `true` to show close icon | `false` * visible | Boolean | `true` to make visible | `false` * * @return {DOMElement} the dialog content root */ create: function(opts) { opts = opts || {}; if (opts.loader) { FB.Dialog._showLoader(opts.onClose); } var dialog = document.createElement('div'), contentRoot = document.createElement('div'), className = 'fb_dialog'; // optional close icon if (opts.closeIcon && opts.onClose) { var closeIcon = document.createElement('a'); closeIcon.className = 'fb_dialog_close_icon'; closeIcon.onclick = opts.onClose; dialog.appendChild(closeIcon); } // handle rounded corners j0nx //#JSCOVERAGE_IF if (FB.Dom.getBrowserType() == 'ie') { className += ' fb_dialog_legacy'; FB.Array.forEach( [ 'vert_left', 'vert_right', 'horiz_top', 'horiz_bottom', 'top_left', 'top_right', 'bottom_left', 'bottom_right' ], function(name) { var span = document.createElement('span'); span.className = 'fb_dialog_' + name; dialog.appendChild(span); } ); } else { className += ' fb_dialog_advanced'; } if (opts.content) { FB.Content.append(opts.content, contentRoot); } dialog.className = className; contentRoot.className = 'fb_dialog_content'; dialog.appendChild(contentRoot); FB.Content.append(dialog); if (opts.visible) { FB.Dialog.show(dialog); } return contentRoot; }, /** * Raises the given dialog. Any active dialogs are automatically lowered. An * active loading indicator is suppressed. An already-lowered dialog will be * raised and it will be put at the top of the stack. A dialog never shown * before will be added to the top of the stack. * * @access protected * @param dialog {DOMElement} a child element of the dialog */ show: function(dialog) { dialog = FB.Dialog._findRoot(dialog); if (dialog) { FB.Dialog._removeStacked(dialog); FB.Dialog._hideLoader(); FB.Dialog._makeActive(dialog); FB.Dialog._stack.push(dialog); } }, /** * Remove the dialog, show any stacked dialogs. * * @access protected * @param dialog {DOMElement} a child element of the dialog */ remove: function(dialog) { dialog = FB.Dialog._findRoot(dialog); if (dialog) { var is_active = FB.Dialog._active == dialog; FB.Dialog._removeStacked(dialog); if (is_active) { if (FB.Dialog._stack.length > 0) { FB.Dialog.show(FB.Dialog._stack.pop()); } else { FB.Dialog._lowerActive(); } } // wait before the actual removal because of race conditions with async // flash crap. seriously, dont ever ask me about it. // if we remove this without deferring, then in IE only, we'll get an // uncatchable error with no line numbers, function names, or stack // traces. the 3 second delay isn't a problem because the
is // already hidden, it's just not removed from the DOM yet. window.setTimeout(function() { dialog.parentNode.removeChild(dialog); }, 3000); } } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.event * @requires fb.prelude fb.array */ // NOTE: We tag this as FB.Event even though it is actually FB.EventProvider to // work around limitations in the documentation system. /** * Event handling mechanism for globally named events. * * @static * @class FB.Event */ FB.provide('EventProvider', { /** * Returns the internal subscriber array that can be directly manipulated by * adding/removing things. * * @access private * @return {Object} */ subscribers: function() { // this odd looking logic is to allow instances to lazily have a map of // their events. if subscribers were an object literal itself, we would // have issues with instances sharing the subscribers when its being used // in a mixin style. if (!this._subscribersMap) { this._subscribersMap = {}; } return this._subscribersMap; }, /** * Subscribe to a given event name, invoking your callback function whenever * the event is fired. * * For example, suppose you want to get notified whenever the session * changes: * * FB.Event.subscribe('auth.sessionChange', function(response) { * // do something with response.session * }); * * Global Events: * * - auth.login -- fired when the user logs in * - auth.logout -- fired when the user logs out * - auth.sessionChange -- fired when the session changes * - auth.statusChange -- fired when the status changes * - xfbml.render -- fired when a call to FB.XFBML.parse() completes * - edge.create -- fired when the user likes something (fb:like) * - comments.add -- fired when the user adds a comment (fb:comments) * - fb.log -- fired on log message * * @access public * @param name {String} Name of the event. * @param cb {Function} The handler function. */ subscribe: function(name, cb) { var subs = this.subscribers(); if (!subs[name]) { subs[name] = [cb]; } else { subs[name].push(cb); } }, /** * Removes subscribers, inverse of [FB.Event.subscribe](FB.Event.subscribe). * * Removing a subscriber is basically the same as adding one. You need to * pass the same event name and function to unsubscribe that you passed into * subscribe. If we use a similar example to * [FB.Event.subscribe](FB.event.subscribe), we get: * * var onSessionChange = function(response) { * // do something with response.session * }; * FB.Event.subscribe('auth.sessionChange', onSessionChange); * * // sometime later in your code you dont want to get notified anymore * FB.Event.unsubscribe('auth.sessionChange', onSessionChange); * * @access public * @param name {String} Name of the event. * @param cb {Function} The handler function. */ unsubscribe: function(name, cb) { var subs = this.subscribers()[name]; FB.Array.forEach(subs, function(value, key) { if (value == cb) { subs[key] = null; } }); }, /** * Repeatedly listen for an event over time. The callback is invoked * immediately when monitor is called, and then every time the event * fires. The subscription is canceled when the callback returns true. * * @access private * @param {string} name Name of event. * @param {function} callback A callback function. Any additional arguments * to monitor() will be passed on to the callback. When the callback returns * true, the monitoring will cease. */ monitor: function(name, callback) { if (!callback()) { var ctx = this, fn = function() { if (callback.apply(callback, arguments)) { ctx.unsubscribe(name, fn); } }; this.subscribe(name, fn); } }, /** * Removes all subscribers for named event. * * You need to pass the same event name that was passed to FB.Event.subscribe. * This is useful if the event is no longer worth listening to and you * believe that multiple subscribers have been set up. * * @access private * @param name {String} name of the event */ clear: function(name) { delete this.subscribers()[name]; }, /** * Fires a named event. The first argument is the name, the rest of the * arguments are passed to the subscribers. * * @access private * @param name {String} the event name */ fire: function() { var args = Array.prototype.slice.call(arguments), name = args.shift(); FB.Array.forEach(this.subscribers()[name], function(sub) { // this is because we sometimes null out unsubscribed rather than jiggle // the array if (sub) { sub.apply(this, args); } }); } }); /** * Event handling mechanism for globally named events. * * @class FB.Event * @extends FB.EventProvider */ FB.provide('Event', FB.EventProvider); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.flash * @requires fb.prelude * fb.qs * fb.content */ /** * Flash Support. * * @class FB.Flash * @static * @access private */ FB.provide('Flash', { // // DYNAMIC DATA // _minVersions: [ [9, 0, 159, 0 ], [10, 0, 22, 87] ], _swfPath: 'swf/XdComm.swf', /** * The onReady callbacks. * * @access private * @type Array */ _callbacks: [], /** * Initialize the SWF. * * @access private */ init: function() { // only initialize once if (FB.Flash._init) { return; } FB.Flash._init = true; // the SWF calls this global function to notify that its ready // FIXME: should allow the SWF to take a flashvar that controls the name // of this function. we should not have any globals other than FB. window.FB_OnFlashXdCommReady = function() { FB.Flash._ready = true; for (var i=0, l=FB.Flash._callbacks.length; i' + '' + '' + '' ); FB.Content.appendHidden(html); }, /** * Check that the minimal version of Flash we need is available. * * @access private * @return {Boolean} true if the minimum version requirements are matched */ hasMinVersion: function() { if (typeof FB.Flash._hasMinVersion === 'undefined') { var versionString, i, l, version = []; try { versionString = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') .GetVariable('$version'); } catch(x) { if (navigator.mimeTypes.length > 0) { var mimeType = 'application/x-shockwave-flash'; if (navigator.mimeTypes[mimeType].enabledPlugin) { var name = 'Shockwave Flash'; versionString = (navigator.plugins[name + ' 2.0'] || navigator.plugins[name]) .description; } } } // take the string and come up with an array of integers: // [10, 0, 22] if (versionString) { var parts = versionString .replace(/\D+/g, ',') .match(/^,?(.+),?$/)[1] .split(','); for (i=0, l=parts.length; i spec[m]) { // better than needed break majorVersion; } } } } } return FB.Flash._hasMinVersion; }, /** * Register a function that needs to ensure Flash is ready. * * @access private * @param cb {Function} the function */ onReady: function(cb) { FB.Flash.init(); if (FB.Flash._ready) { // this forces the cb to be asynchronous to ensure no one relies on the // _potential_ synchronous nature. window.setTimeout(cb, 0); } else { FB.Flash._callbacks.push(cb); } } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * JavaScript library providing Facebook Connect integration. * * @provides fb.init * @requires fb.prelude * fb.auth * fb.api * fb.cookie * fb.ui * fb.xd */ /** * This is the top level for all the public APIs. * * @class FB * @static * @access public */ FB.provide('', { /** * Initialize the library. * * Typical initialization enabling all optional features: * *
* * * * The best place to put this code is right before the closing * `` tag. * * ### Asynchronous Loading * * The library makes non-blocking loading of the script easy to use by * providing the `fbAsyncInit` hook. If this global function is defined, it * will be executed when the library is loaded: * *
* * * The best place to put the asynchronous version of the code is right after * the opening `` tag. This allows Facebook initialization to happen in * parallel with the initialization on the rest of your page. * * ### Internationalization * * Facebook Connect features are available many locales. You can replace the * `en_US` locale specifed above with one of the [supported Facebook * Locales][locales]. For example, to load up the library and trigger dialogs, * popups and plugins to be in Hindi (`hi_IN`), you can load the library from * this URL: * * http://connect.facebook.net/hi_IN/all.js * * [locales]: http://wiki.developers.facebook.com/index.php/Facebook_Locales * * ### SSL * * Facebook Connect is also available over SSL. You should only use this when * your own page is served over `https://`. The library will rely on the * current page protocol at runtime. The SSL URL is the same, only the * protocol is changed: * * https://connect.facebook.net/en_US/all.js * * **Note**: Some [UI methods][FB.ui] like **stream.publish** and * **stream.share** can be used without registering an application or calling * this method. If you are using an appId, all methods **must** be called * after this method. * * [FB.ui]: /docs/reference/javascript/FB.ui * * @access public * @param options {Object} * * Property | Type | Description | Argument | Default * -------- | ------- | ------------------------------------ | ---------- | ------- * appId | String | Your application ID. | *Optional* | `null` * cookie | Boolean | `true` to enable cookie support. | *Optional* | `false` * logging | Boolean | `false` to disable logging. | *Optional* | `true` * session | Object | Use specified session object. | *Optional* | `null` * status | Boolean | `true` to fetch fresh status. | *Optional* | `false` * xfbml | Boolean | `true` to parse [[wiki:XFBML]] tags. | *Optional* | `false` */ init: function(options) { // only need to list values here that do not already have a falsy default. // this is why cookie/session/status are not listed here. options = FB.copy(options || {}, { logging: true }); FB._apiKey = options.appId || options.apiKey; // disable logging if told to do so, but only if the url doesnt have the // token to turn it on. this allows for easier debugging of third party // sites even if logging has been turned off. if (!options.logging && window.location.toString().indexOf('fb_debug=1') < 0) { FB._logging = false; } FB.XD.init(options.channelUrl); if (FB._apiKey) { // enable cookie support if told to do so FB.Cookie.setEnabled(options.cookie); // if an explicit session was not given, try to _read_ an existing cookie. // we dont enable writing automatically, but we do read automatically. options.session = options.session || FB.Cookie.load(); // set the session FB.Auth.setSession(options.session, options.session ? 'connected' : 'unknown'); // load a fresh session if requested if (options.status) { FB.getLoginStatus(); } } // weak dependency on XFBML if (options.xfbml) { // do this in a setTimeout to delay it until the current call stack has // finished executing window.setTimeout(function() { if (FB.XFBML) { FB.Dom.ready(FB.XFBML.parse); } }, 0); } } }); // this is useful when the library is being loaded asynchronously // // we do it in a setTimeout to wait until the current event loop as finished. // this allows potential library code being included below this block (possible // when being served from an automatically combined version) window.setTimeout(function() { if (window.fbAsyncInit) { fbAsyncInit(); }}, 0); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * Contains the public method ``FB.Insights.impression`` for analytics pixel * * @provides fb.insights * @requires fb.prelude */ /** * Analytics pixel calls. If you are unsure about the potential that * integrating Facebook could provide your application, you can use this light * weight image beacon to collect some insights. * * TODO: Where does one go to look at this data? * * @class FB.Insights * @static * @access private */ FB.provide('Insights', { /** * This method should be called once by each page where you want to track * impressions. * * FB.Insights.impression( * { * api_key: 'API_KEY', * lid: 'EVENT_TYPE' * } * ); * * @access private * @param params {Object} parameters for the impression * @param cb {Function} optional - called with the result of the action */ impression: function(params, cb) { // no http or https so browser will use protocol of current page // see http://www.faqs.org/rfcs/rfc1808.html var g = FB.guid(), u = "//ah8.facebook.com/impression.php/" + g + "/", i = new Image(1, 1), s = []; if (!params.api_key && FB._apiKey) { params.api_key = FB._apiKey; } for (var k in params) { s.push(encodeURIComponent(k) + '=' + encodeURIComponent(params[k])); } u += '?' + s.join('&'); if (cb) { i.onload = cb; } i.src = u; } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.intl * @requires fb.prelude */ /** * Provides i18n machinery. * * @class FB.Intl * @static * @access private */ FB.provide('Intl', { /** * Regular expression snippet containing all the characters that we * count as sentence-final punctuation. */ _punctCharClass: ( '[' + '.!?' + '\u3002' + // Chinese/Japanese period '\uFF01' + // Fullwidth exclamation point '\uFF1F' + // Fullwidth question mark '\u0964' + // Hindi "full stop" '\u2026' + // Chinese ellipsis '\u0EAF' + // Laotian ellipsis '\u1801' + // Mongolian ellipsis '\u0E2F' + // Thai ellipsis '\uFF0E' + // Fullwidth full stop ']' ), /** * Checks whether a string ends in sentence-final punctuation. This logic is * about the same as the PHP ends_in_punct() function; it takes into account * the fact that we consider a string like "foo." to end with a period even * though there's a quote mark afterward. */ _endsInPunct: function(str) { if (typeof str != 'string') { return false; } return str.match(new RegExp( FB.Intl._punctCharClass + '[' + ')"' + "'" + // JavaScript doesn't support Unicode character // properties in regexes, so we have to list // all of these individually. This is an // abbreviated list of the "final punctuation" // and "close punctuation" Unicode codepoints, // excluding symbols we're unlikely to ever // see (mathematical notation, etc.) '\u00BB' + // Double angle quote '\u0F3B' + // Tibetan close quote '\u0F3D' + // Tibetan right paren '\u2019' + // Right single quote '\u201D' + // Right double quote '\u203A' + // Single right angle quote '\u3009' + // Right angle bracket '\u300B' + // Right double angle bracket '\u300D' + // Right corner bracket '\u300F' + // Right hollow corner bracket '\u3011' + // Right lenticular bracket '\u3015' + // Right tortoise shell bracket '\u3017' + // Right hollow lenticular bracket '\u3019' + // Right hollow tortoise shell '\u301B' + // Right hollow square bracket '\u301E' + // Double prime quote '\u301F' + // Low double prime quote '\uFD3F' + // Ornate right parenthesis '\uFF07' + // Fullwidth apostrophe '\uFF09' + // Fullwidth right parenthesis '\uFF3D' + // Fullwidth right square bracket '\s' + ']*$' )); }, /** * i18n string formatting * * @param str {String} the string id * @param args {Object} the replacement tokens */ _tx: function (str, args) { // Does the token substitution for tx() but without the string lookup. // Used for in-place substitutions in translation mode. if (args !== undefined) { if (typeof args != 'object') { FB.log( 'The second arg to FB.Intl._tx() must be an Object for ' + 'tx(' + str + ', ...)' ); } else { var regexp; for (var key in args) { if (args.hasOwnProperty(key)) { // _tx("You are a {what}.", {what:'cow!'}) should be "You are a // cow!" rather than "You are a cow!." if (FB.Intl._endsInPunct(args[key])) { // Replace both the token and the sentence-final punctuation // after it, if any. regexp = new RegExp('\{' + key + '\}' + FB.Intl._punctCharClass + '*', 'g'); } else { regexp = new RegExp('\{' + key + '\}', 'g'); } str = str.replace(regexp, args[key]); } } } } return str; }, /** * i18n string formatting * * @access private * @param str {String} the string id * @param args {Object} the replacement tokens */ tx: function (str, args) { // this is replaced by the i18n machinery when the resources are localized function tx(str, args) { void(0); } // Fail silently if the string table isn't defined. This behaviour is used // when a developer chooses the host the library themselves, rather than // using the one served from facebook. if (!FB.Intl._stringTable) { return null; } return FBIntern.Intl._tx(FB.Intl._stringTable[str], args); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.json * @requires fb.prelude * fb.thirdparty.json2 */ /** * Simple wrapper around standard JSON to handle third-party library quirks. * * @class FB.JSON * @static * @access private */ FB.provide('JSON', { /** * Stringify an object. * * @param obj {Object} the input object * @return {String} the JSON string */ stringify: function(obj) { // PrototypeJS is incompatible with native JSON or JSON2 (which is what // native JSON is based on) if (window.Prototype && Object.toJSON) { return Object.toJSON(obj); } else { return JSON.stringify(obj); } }, /** * Parse a JSON string. * * @param str {String} the JSON string * @param {Object} the parsed object */ parse: function(str) { return JSON.parse(str); }, /** * Flatten an object to "stringified" values only. This is useful as a * pre-processing query strings where the server expects query parameter * values to be JSON encoded. * * @param obj {Object} the input object * @return {Object} object with only string values */ flatten: function(obj) { var flat = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { var value = obj[key]; if (null === value || undefined === value) { continue; } else if (typeof value == 'string') { flat[key] = value; } else { flat[key] = FB.JSON.stringify(value); } } } return flat; } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.qs * @requires fb.prelude fb.array */ /** * Query String encoding & decoding. * * @class FB.QS * @static * @access private */ FB.provide('QS', { /** * Encode parameters to a query string. * * @access private * @param params {Object} the parameters to encode * @param sep {String} the separator string (defaults to '&') * @param encode {Boolean} indicate if the key/value should be URI encoded * @return {String} the query string */ encode: function(params, sep, encode) { sep = sep === undefined ? '&' : sep; encode = encode === false ? function(s) { return s; } : encodeURIComponent; var pairs = []; FB.Array.forEach(params, function(val, key) { if (val !== null && typeof val != 'undefined') { pairs.push(encode(key) + '=' + encode(val)); } }); pairs.sort(); return pairs.join(sep); }, /** * Decode a query string into a parameters object. * * @access private * @param str {String} the query string * @return {Object} the parameters to encode */ decode: function(str) { var decode = decodeURIComponent, params = {}, parts = str.split('&'), i, pair; for (i=0; i display=iframe. Most of the old Connect stuff uses * dialog, but UI Server uses iframe. * 2) Renaming of channel_url parameter to channel. */ genericTransform: function(call) { if (call.params.display == 'dialog') { call.params.display = 'iframe'; call.params.channel = FB.UIServer._xdChannelHandler( call.id, 'parent.parent' ); } return call; }, /** * Prepares a generic UI call. * * @access private * @param params {Object} the user supplied parameters * @param cb {Function} the response callback * @returns {Object} the call data */ prepareCall: function(params, cb) { var method = FB.UIServer.Methods[params.method.toLowerCase()], id = FB.guid(); if (!method) { FB.log('"' + params.method.toLowerCase() + '" is an unknown method.'); return; } // default stuff FB.copy(params, { api_key : FB._apiKey, // TODO change "dialog" to "iframe" once moved to uiserver display : FB._session ? 'dialog' : 'popup', locale : FB._locale, sdk : 'joey', session_key : FB._session && FB._session.session_key }); // cannot use an iframe "dialog" if a session is not available if (!FB._session && params.display == 'dialog' && !method.loggedOutIframe) { FB.log('"dialog" mode can only be used when the user is connected.'); params.display = 'popup'; } // the basic call data var call = { cb : cb, id : id, size : method.size || {}, url : FB._domain.www + method.url, params : params }; // optional method transform if (method.transform) { call = method.transform(call); // nothing returned from a transform means we abort if (!call) { return; } } // setting these after to ensure the value is based on the final // params.display value var relation = call.params.display == 'popup' ? 'opener' : 'parent'; if (!(call.id in FB.UIServer._defaultCb) && !('next' in call.params)) { call.params.next = FB.UIServer._xdResult( call.cb, call.id, relation, true // isDefault ); } if (relation === 'parent') { call.params.channel_url = FB.UIServer._xdChannelHandler( id, 'parent.parent' ); } // set this at the end to include all possible params var encodedQS = FB.QS.encode(FB.JSON.flatten(call.params)); if ((call.url + encodedQS).length > 2000) { call.post = true; } else { if (encodedQS) { call.url += '?' + encodedQS; } } return call; }, /** * Open a popup window with the given url and dimensions and place it at the * center of the current window. * * @access private * @param call {Object} the call data */ popup: function(call) { // we try to place it at the center of the current window var screenX = typeof window.screenX != 'undefined' ? window.screenX : window.screenLeft, screenY = typeof window.screenY != 'undefined' ? window.screenY : window.screenTop, outerWidth = typeof window.outerWidth != 'undefined' ? window.outerWidth : document.documentElement.clientWidth, outerHeight = typeof window.outerHeight != 'undefined' ? window.outerHeight : (document.documentElement.clientHeight - 22), // 22= IE toolbar height width = call.size.width, height = call.size.height, left = parseInt(screenX + ((outerWidth - width) / 2), 10), top = parseInt(screenY + ((outerHeight - height) / 2.5), 10), features = ( 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top ); // either a empty window and then a POST, or a direct GET to the full url if (call.post) { FB.UIServer._active[call.id] = window.open( 'about:blank', call.id, features ); FB.Content.postTarget({ url : call.url, target : call.id, params : call.params }); } else { FB.UIServer._active[call.id] = window.open( call.url, call.id, features ); } // if there's a default close action, setup the monitor for it if (call.id in FB.UIServer._defaultCb) { FB.UIServer._popupMonitor(); } }, /** * Builds and inserts a hidden iframe based on the given call data. * * @access private * @param call {Object} the call data */ hidden: function(call) { call.className = 'FB_UI_Hidden'; call.root = FB.Content.appendHidden(''); FB.UIServer._insertIframe(call); }, /** * Builds and inserts a iframe dialog based on the given call data. * * @access private * @param call {Object} the call data */ iframe: function(call) { call.className = 'FB_UI_Dialog'; call.root = FB.Dialog.create({ onClose: function() { FB.UIServer._triggerDefault(call.id); }, loader: true, closeIcon: true }); FB.Dom.addCss(call.root, 'fb_dialog_iframe'); FB.UIServer._insertIframe(call); }, /** * Inserts an iframe based on the given call data. * * @access private * @param call {Object} the call data */ _insertIframe: function(call) { // either a empty iframe and then a POST, or a direct GET to the full url if (call.post) { FB.Content.insertIframe({ url : 'about:blank', root : call.root, className : call.className, width : call.size.width, height : call.size.height, onload : function(node) { FB.UIServer._active[call.id] = node; FB.Content.postTarget({ url : call.url, target : node.name, params : call.params }); } }); } else { FB.Content.insertIframe({ url : call.url, root : call.root, className : call.className, width : call.size.width, height : call.size.height, onload : function(node) { FB.UIServer._active[call.id] = node; } }); } }, /** * Trigger the default action for the given call id. * * @param id {String} the call id */ _triggerDefault: function(id) { FB.UIServer._xdRecv( { frame: id }, FB.UIServer._defaultCb[id] || function() {} ); }, /** * Start and manage the window monitor interval. This allows us to invoke * the default callback for a window when the user closes the window * directly. * * @access private */ _popupMonitor: function() { // check all open windows var found; for (var id in FB.UIServer._active) { // ignore prototype properties, and ones without a default callback if (FB.UIServer._active.hasOwnProperty(id) && id in FB.UIServer._defaultCb) { var win = FB.UIServer._active[id]; // ignore iframes try { if (win.tagName) { // is an iframe, we're done continue; } } catch (x) { // probably a permission error } try { // found a closed window if (win.closed) { FB.UIServer._triggerDefault(id); } else { found = true; // need to monitor this open window } } catch (y) { // probably a permission error } } } if (found && !FB.UIServer._popupInterval) { // start the monitor if needed and it's not already running FB.UIServer._popupInterval = window.setInterval( FB.UIServer._popupMonitor, 100 ); } else if (!found && FB.UIServer._popupInterval) { // shutdown if we have nothing to monitor but it's running window.clearInterval(FB.UIServer._popupInterval); FB.UIServer._popupInterval = null; } }, /** * Handles channel messages. These should be general, like a resize message. * Custom logic should be handled as part of the "next" handler. * * @access private * @param frame {String} the frame id * @param relation {String} the frame relation * @return {String} the handler url */ _xdChannelHandler: function(frame, relation) { return FB.XD.handler(function(data) { var node = FB.UIServer._active[frame]; if (!node) { // dead handler return; } if (data.type == 'resize') { if (data.height) { node.style.height = data.height + 'px'; } if (data.width) { node.style.width = data.width + 'px'; } FB.Dialog.show(node); } }, relation, true); }, /** * A "next handler" is a specialized XD handler that will also close the * frame. This can be a hidden iframe, iframe dialog or a popup window. * * @access private * @param cb {Function} the callback function * @param frame {String} frame id for the callback will be used with * @param relation {String} parent or opener to indicate window relation * @param isDefault {Boolean} is this the default callback for the frame * @return {String} the xd url bound to the callback */ _xdNextHandler: function(cb, frame, relation, isDefault) { if (isDefault) { FB.UIServer._defaultCb[frame] = cb; } return FB.XD.handler(function(data) { FB.UIServer._xdRecv(data, cb); }, relation) + '&frame=' + frame; }, /** * Handles the parsed message, invokes the bound callback with the data and * removes the related window/frame. This is the asynchronous entry point for * when a message arrives. * * @access private * @param data {Object} the message parameters * @param cb {Function} the callback function */ _xdRecv: function(data, cb) { var frame = FB.UIServer._active[data.frame]; // iframe try { if (FB.Dom.containsCss(frame, 'FB_UI_Hidden')) { // wait before the actual removal because of race conditions with async // flash crap. seriously, dont ever ask me about it. window.setTimeout(function() { // remove iframe's parentNode to match what FB.UIServer.hidden() does frame.parentNode.parentNode.removeChild(frame.parentNode); }, 3000); } else if (FB.Dom.containsCss(frame, 'FB_UI_Dialog')) { FB.Dialog.remove(frame); } } catch (x) { // do nothing, permission error } // popup window try { if (frame.close) { frame.close(); FB.UIServer._popupCount--; } } catch (y) { // do nothing, permission error } // cleanup and fire delete FB.UIServer._active[data.frame]; delete FB.UIServer._defaultCb[data.frame]; cb(data); }, /** * Some Facebook redirect URLs use a special ``xxRESULTTOKENxx`` to return * custom values. This is a convenience function to wrap a callback that * expects this value back. * * @access private * @param cb {Function} the callback function * @param frame {String} the frame id for the callback is tied to * @param target {String} parent or opener to indicate window relation * @param isDefault {Boolean} is this the default callback for the frame * @return {String} the xd url bound to the callback */ _xdResult: function(cb, frame, target, isDefault) { return ( FB.UIServer._xdNextHandler(function(params) { cb && cb(params.result && params.result != FB.UIServer._resultToken && JSON.parse(params.result)); }, frame, target, isDefault) + '&result=' + encodeURIComponent(FB.UIServer._resultToken) ); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.ui.methods * @requires fb.prelude * fb.ui */ /** * Simple UI methods. Consider putting complex UI methods in their own modules. * * NOTE: Right now, Methods need to provide an initial size, as well as a URL. * In the UIServer enabled world, we should not need the URL. */ FB.provide('UIServer.Methods', { 'friends.add': { size : { width: 575, height: 240 }, url : 'connect/uiserver.php', transform : FB.UIServer.genericTransform }, 'stream.publish': { size : { width: 575, height: 240 }, url : 'connect/prompt_feed.php', transform: function(call) { var cb = call.cb; call.cb = function(result) { if (result) { if (result.postId) { result = { post_id: result.postId }; } else { result = null; } } cb && cb(result); }; call.params.callback = FB.UIServer._xdResult( call.cb, call.id, call.params.display == 'popup' ? 'opener' : 'parent', true ); return call; } }, 'stream.share': { size : { width: 575, height: 380 }, url : 'sharer.php', transform : function(call) { if (!call.params.u) { call.params.u = window.location.toString(); } return call; } }, 'fbml.dialog': { size : { width: 575, height: 300 }, url : 'render_fbml.php', loggedOutIframe : true }, 'bookmark.add': { size : { width: 460, height: 226 }, url : 'connect/uiserver.php', transform : FB.UIServer.genericTransform }, 'profile.addtab': { size : { width: 460, height: 226 }, url : 'connect/uiserver.php', transform : FB.UIServer.genericTransform } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.xd * @requires fb.prelude * fb.qs * fb.flash */ /** * The cross domain communication layer. * * @class FB.XD * @static * @access private */ FB.provide('XD', { _origin : null, _transport : null, _callbacks : {}, _forever : {}, /** * Initialize the XD layer. Native postMessage or Flash is required. * * @param channelUrl {String} optional channel URL * @access private */ init: function(channelUrl) { // only do init once, if this is set, we're already done if (FB.XD._origin) { return; } // We currently disable postMessage in IE8 because it does not work with // window.opener. We can probably be smarter about it. //#JSCOVERAGE_IF if (window.addEventListener && window.postMessage) { // The origin here is used for postMessage security. It needs to be based // on the URL of the current window. It is required and validated by // Facebook as part of the xd_proxy.php. FB.XD._origin = (window.location.protocol + '//' + window.location.host + '/' + FB.guid()); FB.XD.PostMessage.init(); FB.XD._transport = 'postmessage'; } else if (!channelUrl && FB.Flash.hasMinVersion()) { // The origin here is used for Flash XD security. It needs to be based on // document.domain rather than the URL of the current window. It is // required and validated by Facebook as part of the xd_proxy.php. FB.XD._origin = (window.location.protocol + '//' + document.domain + '/' + FB.guid()); FB.XD.Flash.init(); FB.XD._transport = 'flash'; } else { FB.XD._transport = 'fragment'; FB.XD.Fragment._channelUrl = channelUrl || window.location.toString(); } }, /** * Resolve a id back to a node. An id is a string like: * top.frames[5].frames['crazy'].parent.frames["two"].opener * * @param id {String} the string to resolve * @returns {Node} the resolved window object * @throws SyntaxError if the id is malformed */ resolveRelation: function(id) { var pt, matches, parts = id.split('.'), node = window; for (var i=0, l=parts.length; i 0) { return 'javascript:false;//'; } // the ?=& tricks login.php into appending at the end instead // of before the fragment as a query string // FIXME var xdProxy = FB._domain.cdn + 'connect/xd_proxy.php#?=&', id = FB.guid(); // in fragment mode, the url is the current page and a fragment with a // magic token if (FB.XD._transport == 'fragment') { xdProxy = FB.XD.Fragment._channelUrl; var poundIndex = xdProxy.indexOf('#'); if (poundIndex > 0) { xdProxy = xdProxy.substr(0, poundIndex); } xdProxy += ( (xdProxy.indexOf('?') < 0 ? '?' : '&') + FB.XD.Fragment._magic + '#?=&' ); } if (forever) { FB.XD._forever[id] = true; } FB.XD._callbacks[id] = cb; return xdProxy + FB.QS.encode({ cb : id, origin : FB.XD._origin, relation : relation || 'opener', transport : FB.XD._transport }); }, /** * Handles the raw or parsed message and invokes the bound callback with * the data and removes the related window/frame. * * @access private * @param data {String|Object} the message fragment string or parameters */ recv: function(data) { if (typeof data == 'string') { data = FB.QS.decode(data); } var cb = FB.XD._callbacks[data.cb]; if (!FB.XD._forever[data.cb]) { delete FB.XD._callbacks[data.cb]; } cb && cb(data); }, /** * Provides Native ``window.postMessage`` based XD support. * * @class FB.XD.PostMessage * @static * @for FB.XD * @access private */ PostMessage: { /** * Initialize the native PostMessage system. * * @access private */ init: function() { var H = FB.XD.PostMessage.onMessage; window.addEventListener ? window.addEventListener('message', H, false) : window.attachEvent('onmessage', H); }, /** * Handles a message event. * * @access private * @param event {Event} the event object */ onMessage: function(event) { FB.XD.recv(event.data); } }, /** * Provides Flash Local Connection based XD support. * * @class FB.XD.Flash * @static * @for FB.XD * @access private */ Flash: { /** * Initialize the Flash Local Connection. * * @access private */ init: function() { FB.Flash.onReady(function() { document.XdComm.postMessage_init('FB.XD.Flash.onMessage', FB.XD._origin); }); }, /** * Handles a message received by the Flash Local Connection. * * @access private * @param message {String} the URI encoded string sent by the SWF */ onMessage: function(message) { FB.XD.recv(decodeURIComponent(message)); } }, /** * Provides XD support via a fragment by reusing the current page. * * @class FB.XD.Fragment * @static * @for FB.XD * @access private */ Fragment: { _magic: 'fb_xd_fragment', /** * Check if the fragment looks like a message, and dispatch if it does. */ checkAndDispatch: function() { var loc = window.location.toString(), fragment = loc.substr(loc.indexOf('#') + 1), magicIndex = loc.indexOf(FB.XD.Fragment._magic); if (magicIndex > 0) { // make these no-op to help with performance // // this works independent of the module being present or not, or being // loaded before or after FB.init = FB.getLoginStatus = FB.api = function() {}; // display none helps prevent loading of some stuff document.documentElement.style.display = 'none'; FB.XD.resolveRelation( FB.QS.decode(fragment).relation).FB.XD.recv(fragment); } } } }); // NOTE: self executing code. // // if the page is being used for fragment based XD messaging, we need to // dispatch on load without needing any API calls. it only does stuff if the // magic token is found in the fragment. FB.XD.Fragment.checkAndDispatch(); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * * @provides fb.compat.ui * @requires fb.prelude * fb.qs * fb.ui * fb.json */ /** * NOTE: You should use FB.ui() instead. * * UI Calls. * * @class FB * @static * @access private */ FB.provide('', { /** * NOTE: You should use FB.ui() instead. * * Sharing is the light weight way of distributing your content. As opposed * to the structured data explicitly given in the [FB.publish][publish] call, * with share you simply provide the URL. * * FB.share('http://github.com/facebook/connect-js'); * * Calling [FB.share][share] without any arguments will share the current * page. * * This call can be used without requiring the user to sign in. * * [publish]: /docs/?u=facebook.jslib-alpha.FB.publish * [share]: /docs/?u=facebook.jslib-alpha.FB.share * * @access private * @param u {String} the url (defaults to current URL) */ share: function(u) { FB.log('FB.share() has been deprecated. Please use FB.ui() instead.'); FB.ui({ display : 'popup', method : 'stream.share', u : u }); }, /** * NOTE: You should use FB.ui() instead. * * Publish a post to the stream. * * This is the main, fully featured distribution mechanism for you * to publish into the user's stream. It can be used, with or * without an API key. With an API key you can control the * Application Icon and get attribution. You must also do this if * you wish to use the callback to get notified of the `post_id` * and the `message` the user typed in the published post, or find * out if the user did not publish (clicked on the skipped button). * * Publishing is a powerful feature that allows you to submit rich * media and provide a integrated experience with control over your * stream post. You can guide the user by choosing the prompt, * and/or a default message which they may customize. In addition, * you may provide image, video, audio or flash based attachments * with along with their metadata. You also get the ability to * provide action links which show next to the "Like" and "Comment" * actions. All this together provides you full control over your * stream post. In addition, if you may also specify a target for * the story, such as another user or a page. * * A post may contain the following properties: * * Property | Type | Description * ------------------- | ------ | -------------------------------------- * message | String | This allows prepopulating the message. * attachment | Object | An [[wiki:Attachment (Streams)]] object. * action_links | Array | An array of [[wiki:Action Links]]. * actor_id | String | A actor profile/page id. * target_id | String | A target profile id. * user_message_prompt | String | Custom prompt message. * * The post and all the parameters are optional, so use what is best * for your specific case. * * Example: * * var post = { * message: 'getting educated about Facebook Connect', * attachment: { * name: 'Facebook Connect JavaScript SDK', * description: ( * 'A JavaScript library that allows you to harness ' + * 'the power of Facebook, bringing the user\'s identity, ' + * 'social graph and distribution power to your site.' * ), * href: 'http://github.com/facebook/connect-js' * }, * action_links: [ * { * text: 'GitHub Repo', * href: 'http://github.com/facebook/connect-js' * } * ], * user_message_prompt: 'Share your thoughts about Facebook Connect' * }; * * FB.publish( * post, * function(published_post) { * if (published_post) { * alert( * 'The post was successfully published. ' + * 'Post ID: ' + published_post.post_id + * '. Message: ' + published_post.message * ); * } else { * alert('The post was not published.'); * } * } * ); * * @access private * @param post {Object} the post object * @param cb {Function} called with the result of the action */ publish: function(post, cb) { FB.log('FB.publish() has been deprecated. Please use FB.ui() instead.'); post = post || {}; FB.ui(FB.copy({ display : 'popup', method : 'stream.publish', preview : 1 }, post || {}), cb); }, /** * NOTE: You should use FB.ui() instead. * * Prompt the user to add the given id as a friend. * * @access private * @param id {String} the id of the target user * @param cb {Function} called with the result of the action */ addFriend: function(id, cb) { FB.log('FB.addFriend() has been deprecated. Please use FB.ui() instead.'); FB.ui({ display : 'popup', id : id, method : 'friend.add' }, cb); } }); /** * Copyright Facebook Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @provides fb.array * @layer basic * @requires fb.prelude */ /** * Array related helper methods. * * @class FB.Array * @private * @static */ FB.provide('Array', { /** * Get index of item inside an array. Return's -1 if element is not found. * * @param arr {Array} Array to look through. * @param item {Object} Item to locate. * @return {Number} Index of item. */ indexOf: function (arr, item) { if (arr.indexOf) { return arr.indexOf(item); } var length = arr.length; if (length) { for (var index = 0; index < length; index++) { if (arr[index] === item) { return index; } } } return -1; }, /** * Merge items from source into target, but only if they dont exist. Returns * the target array back. * * @param target {Array} Target array. * @param source {Array} Source array. * @return {Array} Merged array. */ merge: function(target, source) { for (var i=0; i < source.length; i++) { if (FB.Array.indexOf(target, source[i]) < 0) { target.push(source[i]); } } return target; }, /** * Create an new array from the given array and a filter function. * * @param arr {Array} Source array. * @param fn {Function} Filter callback function. * @return {Array} Filtered array. */ filter: function(arr, fn) { var b = []; for (var i=0; i < arr.length; i++) { if (fn(arr[i])) { b.push(arr[i]); } } return b; }, /** * Create an array from the keys in an object. * * Example: keys({'x': 2, 'y': 3'}) returns ['x', 'y'] * * @param obj {Object} Source object. * @param proto {Boolean} Specify true to include inherited properties. * @return {Array} The array of keys. */ keys: function(obj, proto) { var arr = []; for (var key in obj) { if (proto || obj.hasOwnProperty(key)) { arr.push(key); } } return arr; }, /** * Create an array by performing transformation on the items in a source * array. * * @param arr {Array} Source array. * @param transform {Function} Transformation function. * @return {Array} The transformed array. */ map: function(arr, transform) { var ret = []; for (var i=0; i < arr.length; i++) { ret.push(transform(arr[i])); } return ret; }, /** * For looping through Arrays and Objects. * * @param {Object} item an Array or an Object * @param {Function} fn the callback function for iteration. * The function will be pass (value, [index/key], item) paramters * @param {Bool} proto indicate if properties from the prototype should * be included * */ forEach: function(item, fn, proto) { if (!item) { return; } if (Object.prototype.toString.apply(item) === '[object Array]' || (!(item instanceof Function) && typeof item.length == 'number')) { if (item.forEach) { item.forEach(fn); } else { for (var i=0, l=item.length; i= 0; }, /** * Add a class to a element. * * @param dom {DOMElement} the element * @param className {String} the class name */ addCss: function(dom, className) { if (!FB.Dom.containsCss(dom, className)) { dom.className = dom.className + ' ' + className; } }, /** * Remove a class from the element. * * @param dom {DOMElement} the element * @param className {String} the class name */ removeCss: function(dom, className) { if (FB.Dom.containsCss(dom, className)) { dom.className = dom.className.replace(className, ''); FB.Dom.removeCss(dom, className); // in case of repetition } }, /** * Returns the computed style for the element * * note: requires browser specific names to be passed for specials * border-radius -> ('-moz-border-radius', 'border-radius') * * @param dom {DOMElement} the element * @param styleProp {String} the property name */ getStyle: function (dom, styleProp) { var y = false, s = dom.style; if (styleProp == 'opacity') { if (s.opacity) { return s.opacity * 100; } if (s.MozOpacity) { return s.MozOpacity * 100; } if (s.KhtmlOpacity) { return s.KhtmlOpacity * 100; } if (s.filters) { return s.filters.alpha.opacity; } return 0; // TODO(alpjor) fix default opacity } else { if (dom.currentStyle) { // camelCase (e.g. 'marginTop') FB.Array.forEach(styleProp.match(/\-([a-z])/g), function(match) { styleProp = styleProp.replace(match, match.substr(1,1).toUpperCase()); }); y = dom.currentStyle[styleProp]; } else { // dashes (e.g. 'margin-top') FB.Array.forEach(styleProp.match(/[A-Z]/g), function(match) { styleProp = styleProp.replace(match, '-'+ match.toLowerCase()); }); if (window.getComputedStyle) { y = document.defaultView .getComputedStyle(dom,null).getPropertyValue(styleProp); // special handling for IE // for some reason it doesn't return '0%' for defaults. so needed to // translate 'top' and 'left' into '0px' if (styleProp == 'background-position-y' || styleProp == 'background-position-x') { if (y == 'top' || y == 'left') { y = '0px'; } } } } } return y; }, /** * Sets the style for the element to value * * note: requires browser specific names to be passed for specials * border-radius -> ('-moz-border-radius', 'border-radius') * * @param dom {DOMElement} the element * @param styleProp {String} the property name * @param value {String} the css value to set this property to */ setStyle: function(dom, styleProp, value) { var s = dom.style; if (styleProp == 'opacity') { if (value >= 100) { value = 99.999; } // fix for Mozilla < 1.5b2 if (value < 0) { value = 0; } s.opacity = value/100; s.MozOpacity = value/100; s.KhtmlOpacity = value/100; if (s.filters) { s.filters.alpha.opacity = value; } } else { s[styleProp] = value; } }, /** * Dynamically add a script tag. * * @param src {String} the url for the script */ addScript: function(src) { var script = document.createElement('script'); script.type = "text/javascript"; script.src = src; return document.getElementsByTagName('HEAD')[0].appendChild(script); }, /** * Add CSS rules using a