/**
* 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
` 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