blob: 3062aed105e69870ef41f6f04d48cc99e1e7427a [file] [log] [blame]
/*1329323125,171364642,JIT Construction: v510186,en_US*/
/**
* 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) {
window.FB = {
// use the init method to set these values correctly
_apiKey : null,
_authResponse : 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.name.indexOf('iframe_canvas') > -1) ||
(window.name.indexOf('app_runner') > -1)),
// Determines if we should use HTTPS when attempting cross-domain
// communication with facebook.com. This is assumed to be the case when
// window.name contains "_fb_https". This value may also be set by the
// response from FB.login() or FB.getLoginStatus()
_https: (window.name.indexOf('_fb_https') > -1),
//
// DYNAMIC DATA
//
// the various domains needed for using Connect
_domain: {
api : 'https://api.facebook.com/',
api_read : 'https://api-read.facebook.com/',
cdn : 'http://static.ak.fbcdn.net/',
https_cdn : 'https://s-static.ak.fbcdn.net/',
graph : 'https://graph.facebook.com/',
staticfb : 'http://static.ak.facebook.com/',
https_staticfb : 'https://s-static.ak.facebook.com/',
www : 'http://www.facebook.com/',
https_www : 'https://www.facebook.com/',
m : 'http://m.facebook.com/',
https_m : 'https://m.facebook.com/'
},
_locale: null,
_localeIsRtl: false,
// CORDOVA PATCH
_nativeInterface : null,
/**
* Retrieve one of the various domains needed for Connect.
*
* @access private
* @param domain (String) The domain to retrieve
* @param noForcedHTTPS (bool) Do not force https domain
*/
getDomain: function(domain, noForcedHTTPS) {
var forceHTTPS = !noForcedHTTPS &&
(window.location.protocol == 'https:' || FB._https);
switch (domain) {
case 'api':
return FB._domain.api;
case 'api_read':
return FB._domain.api_read;
case 'cdn':
return forceHTTPS ? FB._domain.https_cdn : FB._domain.cdn;
case 'cdn_foreign':
return FB._domain.cdn_foreign;
case 'https_cdn':
return FB._domain.https_cdn;
case 'graph':
return FB._domain.graph;
case 'staticfb':
return forceHTTPS ? FB._domain.https_staticfb : FB._domain.staticfb;
case 'https_staticfb':
return FB._domain.https_staticfb;
case 'www':
return forceHTTPS ? FB._domain.https_www : FB._domain.www;
case 'https_www':
return FB._domain.https_www;
case 'm':
return forceHTTPS ? FB._domain.https_m : FB._domain.m;
case 'https_m':
return FB._domain.https_m;
}
},
/**
* 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 transform {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.<target>.
* 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.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) parameters
* @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<l; i++) {
fn(item[i], i, item);
}
}
} else {
for (var key in item) {
if (proto || item.hasOwnProperty(key)) {
fn(item[key], key, item);
}
}
}
},
/**
* Turns HTMLCollections or anything array-like (that has a `length`)
* such as function `arguments` into a real array
*
* @param {HTMLCollection} coll Array-like collection
* @return {Array}
*/
toArray: function(coll) {
for (var i = 0, a = [], len = coll.length; i < len; i++) {
a[i] = coll[i];
}
return a;
}
});
/**
* 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<parts.length; i++) {
pair = parts[i].split('=', 2);
if (pair && pair[0]) {
params[decode(pair[0])] = decode(pair[1] || '');
}
}
return params;
}
});
/**
* 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
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
* onInsert Function optional callback directly after insertion
* 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) {
// Initial src is set to javascript:false so as to not trigger the
// unsecure content warning.
var html = (
'<iframe' +
' id="' + opts.id + '"' +
' name="' + opts.name + '"' +
(opts.title ? ' title="' + opts.title + '"' : '') +
(opts.className ? ' class="' + opts.className + '"' : '') +
' style="border:none;' +
(opts.width ? 'width:' + opts.width + 'px;' : '') +
(opts.height ? 'height:' + opts.height + 'px;' : '') +
'"' +
' src="javascript:false;"' +
' frameborder="0"' +
' scrolling="no"' +
' allowtransparency="true"' +
' onload="FB.Content._callbacks.' + guid + '()"' +
'></iframe>'
);
// 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 = '<iframe src="javascript:false"'+
' frameborder="0"'+
' scrolling="no"'+
' style="height:1px"></iframe>';
// 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;
opts.root.firstChild.src = opts.url;
opts.onInsert && opts.onInsert(opts.root.firstChild);
}, 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.scrolling = 'no';
node.style.border = 'none';
node.style.overflow = 'hidden';
if (opts.title) {
node.title = opts.title;
}
if (opts.className) {
node.className = opts.className;
}
if (opts.height) {
node.style.height = opts.height + 'px';
}
if (opts.width) {
if (opts.width == '100%') {
node.style.width = opts.width;
} else {
node.style.width = opts.width + 'px';
}
}
opts.root.appendChild(node);
// Now we'll be setting the real src.
srcSet = true;
node.src = opts.url;
opts.onInsert && opts.onInsert(node);
}
},
/**
* Dynamically generate a <form> and submits it to the given target.
* Uses POST by default.
*
* The opts MUST contain:
* url String action URL for the form
* target String the target for the form
* params Object the key/values to be used as POST input
*
* @access protected
* @param opts {Object} the options
* @param get Should we use get instead?
*/
submitToTarget: function(opts, get) {
var form = document.createElement('form');
form.action = opts.url;
form.target = opts.target;
form.method = (get) ? 'GET' : 'POST';
FB.Content.appendHidden(form);
FB.Array.forEach(opts.params, function(val, key) {
if (val !== null && val !== undefined) {
var input = document.createElement('input');
input.name = key;
input.value = val;
form.appendChild(input);
}
});
form.submit();
form.parentNode.removeChild(form);
}
});
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*
* @provides fb.flash
* @requires fb.prelude
* fb.qs
* fb.content
*/
/**
* Flash Support.
*
* @class FB.Flash
* @static
* @access private
*/
FB.provide('Flash', {
//
// DYNAMIC DATA
//
_minVersions: [
[9, 0, 159, 0 ],
[10, 0, 22, 87]
],
_swfPath: 'swf/XdComm.swf',
/**
* The onReady callbacks.
*
* @access private
* @type Array
*/
_callbacks: [],
/**
* Names of embedded swfs. Used for removing on unload.
*
* @access private
* @type Object
*/
_names: {},
/**
* Whether or not unload callback has been registered (used in IE9).
*
* @access private
* @type Boolean
*/
_unloadRegistered: false,
/**
* Initialize the SWF.
*
* @access private
*/
init: function() {
// only initialize once
if (FB.Flash._init) {
return;
}
FB.Flash._init = true;
// the SWF calls this global function to notify that its ready
// FIXME: should allow the SWF to take a flashvar that controls the name
// of this function. we should not have any globals other than FB.
window.FB_OnFlashXdCommReady = function() {
FB.Flash._ready = true;
for (var i=0, l=FB.Flash._callbacks.length; i<l; i++) {
FB.Flash._callbacks[i]();
}
FB.Flash._callbacks = [];
};
FB.Flash.embedSWF('XdComm',
FB.getDomain('cdn_foreign') + FB.Flash._swfPath);
},
/**
* generates the swf <object> tag and drops it in the DOM
*
* @access private
*/
embedSWF: function(name, swf, flashvars) {
// create the swf
var
IE = !!document.attachEvent,
html = (
'<object ' +
'type="application/x-shockwave-flash" ' +
'id="' + name + '" ' +
(flashvars ? 'flashvars="' + flashvars + '" ' : '') +
(IE ? 'name="' + name + '" ' : '') +
(IE ? '' : 'data="' + swf + '" ') +
(IE
? 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" '
: ''
) +
'allowscriptaccess="always">' +
'<param name="movie" value="' + swf + '"></param>' +
'<param name="allowscriptaccess" value="always"></param>' +
'</object>'
);
FB.Content.appendHidden(html);
if (FB.UA.ie() >= 9) {
if (!FB.Flash._unloadRegistered) {
var unloadcb = function() {
FB.Array.forEach(FB.Flash._names, function(val, key) {
var elem = document.getElementById(key);
if (elem) {
elem.removeNode(true);
}
});
};
window.attachEvent('onunload', unloadcb);
FB.Flash._unloadRegistered = true;
}
FB.Flash._names[name] = true;
}
},
/**
* Check that the minimal version of Flash we need is available.
*
* @access private
* @return {Boolean} true if the minimum version requirements are matched
*/
hasMinVersion: function() {
if (typeof FB.Flash._hasMinVersion === 'undefined') {
var
versionString,
i,
l,
version = [];
try {
versionString = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
.GetVariable('$version');
} catch(x) {
if (navigator.mimeTypes.length > 0) {
var mimeType = 'application/x-shockwave-flash';
if (navigator.mimeTypes[mimeType].enabledPlugin) {
var name = 'Shockwave Flash';
versionString = (navigator.plugins[name + ' 2.0'] ||
navigator.plugins[name])
.description;
}
}
}
// take the string and come up with an array of integers:
// [10, 0, 22]
if (versionString) {
var parts = versionString
.replace(/\D+/g, ',')
.match(/^,?(.+),?$/)[1]
.split(',');
for (i=0, l=parts.length; i<l; i++) {
version.push(parseInt(parts[i], 10));
}
}
// start by assuming we dont have the min version.
FB.Flash._hasMinVersion = false;
// look through all the allowed version definitions.
majorVersion:
for (i=0, l=FB.Flash._minVersions.length; i<l; i++) {
var spec = FB.Flash._minVersions[i];
// we only accept known major versions, and every supported major
// version has at least one entry in _minVersions. only if the major
// version matches, does the rest of the check make sense.
if (spec[0] != version[0]) {
continue;
}
// the rest of the version components must be equal or higher
for (var m=1, n=spec.length, o=version.length; (m<n && m<o); m++) {
if (version[m] < spec[m]) {
// less means this major version is no good
//#JSCOVERAGE_IF 0
FB.Flash._hasMinVersion = false;
continue majorVersion;
//#JSCOVERAGE_ENDIF
} else {
FB.Flash._hasMinVersion = true;
if (version[m] > spec[m]) {
// better than needed
break majorVersion;
}
}
}
}
}
return FB.Flash._hasMinVersion;
},
/**
* Register a function that needs to ensure Flash is ready.
*
* @access private
* @param cb {Function} the function
*/
onReady: function(cb) {
FB.Flash.init();
if (FB.Flash._ready) {
// this forces the cb to be asynchronous to ensure no one relies on the
// _potential_ synchronous nature.
window.setTimeout(cb, 0);
} else {
FB.Flash._callbacks.push(cb);
}
}
});
/**
* 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 '&nbsp;'),
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.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.
*
*
*
* 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<l; i++) {
* var post = response[i];
* if (post.message) {
* alert('Message: ' + post.message);
* } else if (post.attachment && post.attachment.name) {
* alert('Attachment: ' + post.attachment.name);
* }
* }
* });
*
* If you have an [authenticated user](FB.login) with the
* [publish_stream](/docs/authentication/permissions) permission, and want
* to publish a new story to their feed:
*
* var body = 'Reading Connect JS documentation';
* FB.api('/me/feed', 'post', { body: body }, function(response) {
* if (!response || response.error) {
* alert('Error occurred');
* } else {
* alert('Post ID: ' + response);
* }
* });
*
* Or if you want a delete a previously published post:
*
* var postId = '1234567890';
* FB.api(postId, 'delete', function(response) {
* if (!response || response.error) {
* alert('Error occurred');
* } else {
* alert('Post was deleted');
* }
* });
*
*
* ### Old REST API calls
*
* This method can also be used to invoke calls to the
* [Old REST API](../rest/). The function signature for invoking REST API
* calls is:
*
* FB.api(params, callback)
*
* For example, to invoke [links.getStats](../rest/links.getStats):
*
* FB.api(
* {
* method: 'links.getStats',
* urls: 'facebook.com,developers.facebook.com'
* },
* function(response) {
* alert(
* 'Total: ' + (response[0].total_count + response[1].total_count));
* }
* );
*
* [names]: https://graph.facebook.com/naitik
* [profilepic]: https://graph.facebook.com/naitik/picture
*
* @access public
* @param path {String} the url path
* @param method {String} the http method (default `"GET"`)
* @param params {Object} the parameters for the query
* @param cb {Function} the callback function to handle the response
*/
api: function() {
if (typeof arguments[0] === 'string') {
FB.ApiServer.graph.apply(FB.ApiServer, arguments);
} else {
FB.ApiServer.rest.apply(FB.ApiServer, arguments);
}
}
});
/**
* API call implementations.
*
* @class FB.ApiServer
* @access private
*/
FB.provide('ApiServer', {
METHODS: ['get', 'post', 'delete', 'put'],
_callbacks: {},
_readOnlyCalls: {
fql_query: true,
fql_multiquery: true,
friends_get: true,
notifications_get: true,
stream_get: true,
users_getinfo: true
},
/**
* Make a API call to Graph server. This is the **real** RESTful API.
*
* Except the path, all arguments to this function are optional. So any of
* these are valid:
*
* FB.api('/me') // throw away the response
* FB.api('/me', function(r) { console.log(r) })
* FB.api('/me', { fields: 'email' }); // throw away response
* FB.api('/me', { fields: 'email' }, function(r) { console.log(r) });
* FB.api('/12345678', 'delete', function(r) { console.log(r) });
* FB.api(
* '/me/feed',
* 'post',
* { body: 'hi there' },
* function(r) { console.log(r) }
* );
*
* @access private
* @param path {String} the url 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
*/
graph: function() {
var
args = Array.prototype.slice.call(arguments),
atoms = args.shift().match(/\/?([^?]*)\??([^#]*)/),
path = atoms[1],
next = args.shift(),
method,
params,
cb;
while (next) {
var type = typeof next;
if (type === 'string' && !method) {
method = next.toLowerCase();
} else if (type === 'function' && !cb) {
cb = next;
} else if (type === 'object' && !params) {
params = next;
} else {
FB.log('Invalid argument passed to FB.api(): ' + next);
return;
}
next = args.shift();
}
method = method || 'get';
params = FB.copy(params || {}, FB.QS.decode(atoms[2]));
if (FB.Array.indexOf(FB.ApiServer.METHODS, method) < 0) {
FB.log('Invalid method passed to FB.api(): ' + method);
return;
}
FB.ApiServer.oauthRequest('graph', path, method, params, cb);
},
/**
* Old school restserver.php calls.
*
* @access private
* @param params {Object} The required arguments vary based on the method
* being used, but specifying the method itself is mandatory:
*
* Property | Type | Description | Argument
* -------- | ------- | -------------------------------- | ------------
* method | String | The API method to invoke. | **Required**
* @param cb {Function} The callback function to handle the response.
*/
rest: function(params, cb) {
var method = params.method.toLowerCase().replace('.', '_');
// this is an optional dependency on FB.Auth
// Auth.revokeAuthorization affects the session
if (FB.Auth && method === 'auth_revokeauthorization') {
var old_cb = cb;
cb = function(response) {
if (response === true) {
FB.Auth.setAuthResponse(null, 'not_authorized');
}
old_cb && old_cb(response);
};
}
params.format = 'json-strings';
params.api_key = FB._apiKey;
var domain = FB.ApiServer._readOnlyCalls[method] ? 'api_read' : 'api';
FB.ApiServer.oauthRequest(domain, 'restserver.php', 'get', params, cb);
},
/**
* Add the oauth parameter, and fire off a request.
*
* @access private
* @param domain {String} the domain key, one of 'api', 'api_read',
* 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
*/
oauthRequest: function(domain, path, method, params, cb) {
if (!params.access_token && FB.getAccessToken()) {
params.access_token = FB.getAccessToken();
}
params.sdk = 'joey';
params.pretty = 0; // browser's default to pretty=1, explicitly setting to
// 0 will save a few bytes
// wrap the callback to force fetch login status if we had a bad access
// token when we made the api call and it hadn't changed between the
// call firing and the response coming in.
var oldCb = cb;
cb = function(response) {
if (FB.Auth && response && FB.getAccessToken() == params.access_token &&
(response.error_code === '190' ||
(response.error &&
(response.error === 'invalid_token' ||
response.error.type === 'OAuthException')))) {
FB.getLoginStatus(null, true);
}
oldCb && oldCb(response);
};
try {
FB.ApiServer.jsonp(domain, path, method, FB.JSON.flatten(params), cb);
} catch (e1_ignore) {
try {
if (!FB.initSitevars.corsKillSwitch &&
FB.ApiServer.corsPost(
domain, path, method, FB.JSON.flatten(params), cb)) {
return;
}
} catch (e2_ignore) {
// do nothing... fall back to flash.
}
if (FB.Flash.hasMinVersion()) {
FB.ApiServer.flash(domain, path, method, FB.JSON.flatten(params), cb);
} else {
throw new Error('Your browser does not support long connect ' +
'requests. You can fix this problem by upgrading your browser ' +
'or installing the latest version of Flash');
}
}
},
corsPost: function(domain, path, method, params, cb) {
var url = FB.getDomain(domain) + path;
if (domain == 'graph') {
params.method = method;
}
var encoded_params = FB.QS.encode(params);
var content_type = 'application/x-www-form-urlencoded';
var request = FB.ApiServer._createCORSRequest('POST', url, content_type);
if (request) {
request.onload = function() {
cb && cb(FB.JSON.parse(request.responseText));
};
request.send(encoded_params);
return true;
} else {
return false;
}
},
_createCORSRequest: function(method, url, content_type) {
if (!window.XMLHttpRequest) {
return null;
}
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
xhr.open(method, url, true);
xhr.setRequestHeader('Content-type', content_type);
} else if (window.XDomainRequest) {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
},
/**
* Basic JSONP Support.
*
* @access private
* @param domain {String} the domain key, one of 'api', 'api_read',
* 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
*/
jsonp: function(domain, path, method, params, cb) {
var
g = FB.guid(),
script = document.createElement('script');
// jsonp needs method overrides as the request itself is always a GET
if (domain === 'graph' && method !== 'get') {
params.method = method;
}
params.callback = 'FB.ApiServer._callbacks.' + g;
var url = (
FB.getDomain(domain) + path +
(path.indexOf('?') > -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() {
if (domain === 'graph') {
params.suppress_http_code = 1;
}
var
url = FB.getDomain(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.event
* @requires fb.prelude fb.array
*/
// NOTE: We tag this as FB.Event even though it is actually FB.EventProvider to
// work around limitations in the documentation system.
/**
* Event handling mechanism for globally named events.
*
* @static
* @class FB.Event
*/
FB.provide('EventProvider', {
/**
* Returns the internal subscriber array that can be directly manipulated by
* adding/removing things.
*
* @access private
* @return {Object}
*/
subscribers: function() {
// this odd looking logic is to allow instances to lazily have a map of
// their events. if subscribers were an object literal itself, we would
// have issues with instances sharing the subscribers when its being used
// in a mixin style.
if (!this._subscribersMap) {
this._subscribersMap = {};
}
return this._subscribersMap;
},
/**
* Subscribe to a given event name, invoking your callback function whenever
* the event is fired.
*
* For example, suppose you want to get notified whenever the authResponse
* changes:
*
* FB.Event.subscribe('auth.authResponse', function(response) {
* // do something with response.access_token
* });
*
* Global Events:
*
* - auth.login -- fired when the user logs in
* - auth.logout -- fired when the user logs out
* - auth.prompt -- fired when the user is prompted to log-in/opt-in
* - auth.authResponseChange -- fired when the authResponse changes
* - auth.accessTokenChange -- fired when the access token changes.
* - auth.statusChange -- fired when the status changes
* - xfbml.parse -- firest when a call to FB.XFBML.parse()
* has processed all XFBML tags in the
* element.process() sense
* - xfbml.render -- fired when a call to FB.XFBML.parse() completes
* - edge.create -- fired when the user likes something (fb:like)
* - comments.add -- fired when the user adds a comment (fb:comments)
* - question.firstVote -- fired when user initially votes on a poll
* (fb:question)
* - question.vote -- fired when user votes again on a poll (fb:question)
* - fb.log -- fired on log message
* - canvas.pageInfoChange -- fired when the page is resized or scrolled
*
* @access public
* @param name {String} Name of the event.
* @param cb {Function} The handler function.
*/
subscribe: function(name, cb) {
var subs = this.subscribers();
if (!subs[name]) {
subs[name] = [cb];
} else {
subs[name].push(cb);
}
},
/**
* Removes subscribers, inverse of [FB.Event.subscribe](FB.Event.subscribe).
*
* Removing a subscriber is basically the same as adding one. You need to
* pass the same event name and function to unsubscribe that you passed into
* subscribe. If we use a similar example to
* [FB.Event.subscribe](FB.event.subscribe), we get:
*
* var onAuthResponseChange = function(response) {
* // do something with response.access_token
* };
* FB.Event.subscribe('auth.authResponseChange', onAuthResponseChange);
*
* // sometime later in your code you dont want to get notified anymore
* FB.Event.unsubscribe('auth.authResponseChange', onAuthResponseChange);
*
* @access public
* @param name {String} Name of the event.
* @param cb {Function} The handler function.
*/
unsubscribe: function(name, cb) {
var subs = this.subscribers()[name];
FB.Array.forEach(subs, function(value, key) {
if (value == cb) {
subs[key] = null;
}
});
},
/**
* Repeatedly listen for an event over time. The callback is invoked
* immediately when monitor is called, and then every time the event
* fires. The subscription is canceled when the callback returns true.
*
* @access private
* @param {string} name Name of event.
* @param {function} callback A callback function. Any additional arguments
* to monitor() will be passed on to the callback. When the callback returns
* true, the monitoring will cease.
*/
monitor: function(name, callback) {
if (!callback()) {
var
ctx = this,
fn = function() {
if (callback.apply(callback, arguments)) {
ctx.unsubscribe(name, fn);
}
};
this.subscribe(name, fn);
}
},
/**
* Removes all subscribers for named event.
*
* You need to pass the same event name that was passed to FB.Event.subscribe.
* This is useful if the event is no longer worth listening to and you
* believe that multiple subscribers have been set up.
*
* @access private
* @param name {String} name of the event
*/
clear: function(name) {
delete this.subscribers()[name];
},
/**
* Fires a named event. The first argument is the name, the rest of the
* arguments are passed to the subscribers.
*
* @access private
* @param name {String} the event name
*/
fire: function() {
var
args = Array.prototype.slice.call(arguments),
name = args.shift();
FB.Array.forEach(this.subscribers()[name], function(sub) {
// this is because we sometimes null out unsubscribed rather than jiggle
// the array
if (sub) {
sub.apply(this, args);
}
});
},
//////////////////////////////////////////////////////////////////////////////
// DOM Events
//////////////////////////////////////////////////////////////////////////////
/**
* Listen to `event` with the `func` event handler.
*/
listen: function(element, event, func) {
if (element.addEventListener) {
element.addEventListener(event, func, false);
} else if (element.attachEvent) {
element.attachEvent('on' + event, func);
}
},
/**
* Do not listen to `event` with the `func` event handler.
*/
unlisten: function(element, event, func) {
if (element.removeEventListener) {
element.removeEventListener(event, func, false);
} else if (element.detachEvent) {
element.detachEvent('on' + event, func);
}
}
});
/**
* Event handling mechanism for globally named events.
*
* @class FB.Event
* @extends FB.EventProvider
*/
FB.provide('Event', FB.EventProvider);
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*
* @provides fb.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 : {},
_xdProxyUrl : 'connect/xd_proxy.php',
// For certain versions of IE, we delay the choice of transport and
// origin until we're in the handler
_openerTransport : null,
_openerOrigin : null,
_nonOpenerOrigin : null,
/**
* 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;
}
//#JSCOVERAGE_IF
// 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.
var postMessageOrigin = (window.location.protocol + '//' +
window.location.host + '/' + FB.guid());
if (window.addEventListener && !window.attachEvent && window.postMessage) {
FB.XD._origin = postMessageOrigin;
FB.XD.PostMessage.init();
FB.XD._transport = 'postmessage';
} else if (!channelUrl && FB.Flash.hasMinVersion()) {
if (document.getElementById('fb-root')) {
var domain = document.domain;
// If we're loading from facebook.com, it's safe to take the entire
// location
if (domain == 'facebook.com') {
domain = window.location.host;
}
// 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 + '//' + domain +
'/' + FB.guid());
FB.XD.Flash.init();
FB.XD._transport = 'flash';
} else {
// if we don't have fb-root, we'll fail miserably
if (FB.log) {
FB.log('missing fb-root, defaulting to fragment-based xdcomm');
}
FB.XD._transport = 'fragment';
FB.XD.Fragment._channelUrl = channelUrl || window.location.toString();
}
} else {
FB.XD._transport = 'fragment';
FB.XD.Fragment._channelUrl = channelUrl || window.location.toString();
}
var IE = !!window.attachEvent;
if (FB.XD._transport != 'postmessage' && IE && window.postMessage) {
// On IE8 and beyond, we can't use postmessage exclusively, but we *may*
// be able to use postMessage below in the handler depending on the
// 'relation' so set up for that. The deal is that we can use postmessage
// on IE that has it, but not for popups (when the relation is 'opener').
FB.XD._openerTransport = FB.XD._transport;
FB.XD._openerOrigin = FB.XD._origin;
FB.XD._nonOpenerOrigin = postMessageOrigin;
}
},
/**
* 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<l; i++) {
pt = parts[i];
if (pt === 'opener' || pt === 'parent' || pt === 'top') {
node = node[pt];
} else if (matches = /^frames\[['"]?([a-zA-Z0-9-_]+)['"]?\]$/.exec(pt)) {
// these regex has the `feature' of fixing some badly quoted strings
node = node.frames[matches[1]];
} else {
throw new SyntaxError('Malformed id to resolve: ' + id + ', pt: ' + pt);
}
}
return node;
},
/**
* Builds a url attached to a callback for xd messages.
*
* This is one half of the XD layer. Given a callback function, we generate
* a xd URL which will invoke the function. This allows us to generate
* redirect urls (used for next/cancel and so on) which will invoke our
* callback functions.
*
* @access private
* @param cb {Function} the callback function
* @param relation {String} parent or opener to indicate window relation
* @param forever {Boolean} indicate this handler needs to live forever
* @param id {string} Optional specified handler id
* @param force_https {Boolean} Optional param to force https
* @return {String} the xd url bound to the callback
*/
handler: function(cb, relation, forever, id, force_https) {
// if for some reason, we end up trying to create a handler on a page that
// is already being used for XD comm as part of the fragment, we simply
// return 'javascript:false' to prevent a recursive page load loop
//
// the // after it makes any appended things to the url become a JS
// comment, and prevents JS parse errors. cloWntoWn.
if (window.location.toString().indexOf(FB.XD.Fragment._magic) > 0) {
return 'javascript:false;//';
}
// allow us to control force secure, which may be necessary for
// plugins for ssl-enabled users on http sites. this is because
// the facebook iframe will load this xd resource
if (FB.initSitevars.forceSecureXdProxy) {
force_https = true;
}
var xdProxy = FB.getDomain((force_https ? 'https_' : '') + 'cdn') +
FB.XD._xdProxyUrl + '#';
id = id || FB.guid();
relation = relation || 'opener';
if (FB.XD._openerTransport) {
// We're set up to swap mechanisms based on 'relation'. We don't
// worry about resetting these at the end, since we'll just set them
// again on the next invocation.
if (relation == 'opener') {
FB.XD._transport = FB.XD._openerTransport;
FB.XD._origin = FB.XD._openerOrigin;
} else {
FB.XD.PostMessage.init();
FB.XD._transport = 'postmessage';
FB.XD._origin = FB.XD._nonOpenerOrigin;
}
}
// 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,
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') {
// Try to determine if the data is in JSON format
try {
data = FB.JSON.parse(data);
} catch (e) {
// If this is not JSON, try FB.QS.decode
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: {
_isInitialized: false,
/**
* Initialize the native PostMessage system.
*
* @access private
*/
init: function() {
if (!FB.XD.PostMessage._isInitialized) {
var H = FB.XD.PostMessage.onMessage;
window.addEventListener
? window.addEventListener('message', H, false)
: window.attachEvent('onmessage', H);
FB.XD.PostMessage._isInitialized = true;
}
},
/**
* Handles a message event.
*
* @access private
* @param event {Event} the event object
*/
onMessage: function(event) {
FB.XD.recv(event.data);
}
},
/**
* Provide support for postMessage between two two webview controls
* running inside the native FB Application on mobile.
*
* @class FB.XD.WebView
* @static
* @for FB.XD
* @access private
*/
WebView: {
onMessage: function(dest, origin, msg) {
FB.XD.recv(msg);
}
},
/**
* 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._openerOrigin ? FB.XD._openerOrigin : 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.ua
* @layer basic
*/
/**
* User Agent and OS detection. Usage is straightforward:
*
* if (FB.UA.ie()) {
* // IE
* }
*
* You can also do version checks:
*
* if (FB.UA.ie() >= 7) {
* // IE7 or better
* }
*
* The browser functions will return NaN if the browser does not match, so
* you can also do version compares the other way:
*
* if (FB.UA.ie() < 7) {
* // IE6 or worse
* }
*
* Note that the version is a float and may include a minor version number,
* so you should always use range operators to perform comparisons, not
* strict equality.
*
* **Note:** You should **strongly** prefer capability detection to browser
* version detection where it's reasonable:
*
* http://www.quirksmode.org/js/support.html
*
* Further, we have a large number of mature wrapper functions and classes
* which abstract away many browser irregularities. Check the documentation,
* grep for things, or ask on javascript@lists.facebook.com before writing yet
* another copy of "event || window.event".
*
* @task browser Determining the User Agent
* @task os Determining the User's Operating System
* @task internal Internal methods
*
* @author marcel, epriestley
*/
FB.provide('UA', {
/**
* Check if the UA is Internet Explorer.
*
* @task browser
* @access public
*
* @return float|NaN Version number (if match) or NaN.
* @author marcel
*/
ie: function() {
return FB.UA._populate() || this._ie;
},
/**
* Check if the UA is Firefox.
*
* @task browser
* @access public
*
* @return float|NaN Version number (if match) or NaN.
* @author marcel
*/
firefox: function() {
return FB.UA._populate() || this._firefox;
},
/**
* Check if the UA is Opera.
*
* @task browser
* @access public
*
* @return float|NaN Version number (if match) or NaN.
* @author marcel
*/
opera: function() {
return FB.UA._populate() || this._opera;
},
/**
* Check if the UA is Safari.
*
* @task browser
* @access public
*
* @return float|NaN Version number (if match) or NaN.
* @author marcel
*/
safari: function() {
return FB.UA._populate() || this._safari;
},
/**
* Check if the UA is a Chrome browser.
*
* @task browser
* @access public
*
* @return float|NaN Version number (if match) or NaN.
* @author cjiang
*/
chrome : function() {
return FB.UA._populate() || this._chrome;
},
/**
* Check if the user is running Windows.
*
* @task os
* @return bool `true' if the user's OS is Windows.
* @author marcel
*/
windows: function() {
return FB.UA._populate() || this._windows;
},
/**
* Check if the user is running Mac OS X.
*
* @task os
* @return bool `true' if the user's OS is Mac OS X.
* @author marcel
*/
osx: function() {
return FB.UA._populate() || this._osx;
},
/**
* Check if the user is running Linux.
*
* @task os
* @return bool `true' if the user's OS is some flavor of Linux.
* @author putnam
*/
linux: function() {
return FB.UA._populate() || this._linux;
},
/**
* Check if the user is running on an iOS platform.
*
* @task os
* @return bool `true' if the user is running some flavor of the
* ios OS.
* @author beng
*/
ios: function() {
FB.UA._populate();
return FB.UA.mobile() && this._ios;
},
/**
* Check if the browser is running inside a smart mobile phone.
* @return bool
* @access public
*/
mobile: function() {
FB.UA._populate();
return !FB._inCanvas && this._mobile;
},
/**
* Checking if we are inside a webview of the FB App for mobile
* @return bool
* @access public
*/
nativeApp: function() {
// Now native FB app generates UA like this:
//
// Mozilla/5.0 (iPhone Simulator; U; CPU iPhone OS 4_2 like Mac OS X; en_IE)
// AppleWebKit (KHTML, like Gecko) Mobile
// [FBAN/FBForIPhone;FBAV/3.5a;FBBV/3500;FBDV/i386;FBMD/
// iPhone Simulator;FBSN/iPhone OS;FBSV/4.2;FBSS/1;FBCR/;
// FBID/phone;FBLC/en_IE]
//
// We will detect by searching for FBAN/\w+;
//
return FB.UA.mobile() && navigator.userAgent.match(/FBAN\/\w+;/i);
},
/**
* Check for the Android browser.
* @return bool
* @access public
*/
android: function() {
FB.UA._populate();
return FB.UA.mobile() && this._android;
},
/**
* Check for the iPad
* @return bool
* @access public
*/
iPad: function() {
FB.UA._populate();
return FB.UA.mobile() && this._iPad;
},
_populated : false,
/**
* Populate the UA and OS information.
*
* @access public
* @task internal
*
* @return void
*
* @author marcel
*/
_populate : function() {
if (FB.UA._populated) {
return;
}
FB.UA._populated = true;
// To work around buggy JS libraries that can't handle multi-digit
// version numbers, Opera 10's user agent string claims it's Opera
// 9, then later includes a Version/X.Y field:
//
// Opera/9.80 (foo) Presto/2.2.15 Version/10.10
// Note: if agent regex is updated, update it in xd_proxy.phpt also!
var agent = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))/.exec(navigator.userAgent);
var os = /(Mac OS X)|(Windows)|(Linux)/.exec(navigator.userAgent);
var ios = /\b(iPhone|iP[ao]d)/.exec(navigator.userAgent);
FB.UA._iPad = /\b(iPad)/.exec(navigator.userAgent);
FB.UA._android = navigator.userAgent.match(/Android/i);
FB.UA._mobile = ios || FB.UA._android ||
navigator.userAgent.match(/Mobile/i);
if (agent) {
FB.UA._ie = agent[1] ? parseFloat(agent[1]) : NaN;
// marcel: IE8 running in IE7 mode.
if (FB.UA._ie >= 8 && !window.HTMLCollection) {
FB.UA._ie = 7;
}
FB.UA._firefox = agent[2] ? parseFloat(agent[2]) : NaN;
FB.UA._opera = agent[3] ? parseFloat(agent[3]) : NaN;
FB.UA._safari = agent[4] ? parseFloat(agent[4]) : NaN;
if (FB.UA._safari) {
// We do not add the regexp to the above test, because it will always
// match 'safari' only since 'AppleWebKit' appears before 'Chrome' in
// the userAgent string.
agent = /(?:Chrome\/(\d+\.\d+))/.exec(navigator.userAgent);
FB.UA._chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
} else {
FB.UA._chrome = NaN;
}
} else {
FB.UA._ie =
FB.UA._firefox =
FB.UA._opera =
FB.UA._chrome =
FB.UA._safari = NaN;
}
if (os) {
FB.UA._osx = !!os[1];
FB.UA._windows = !!os[2];
FB.UA._linux = !!os[3];
} else {
FB.UA._osx =
FB.UA._windows =
FB.UA._linux = false;
}
FB.UA._ios = ios;
}
});
/**
* 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.arbiter
* @requires fb.prelude
* fb.array
* fb.canvas
* fb.content
* fb.json
* fb.qs
* fb.xd
* fb.ua
*/
/**
* Calls Arbiter in the parent Facebook window from inside an iframe
*
* @class FB.Arbiter
* @static
* @access private
*/
FB.provide('Arbiter', {
_canvasProxyUrl: 'connect/canvas_proxy.php',
BEHAVIOR_EVENT: 'e',
BEHAVIOR_PERSISTENT: 'p',
BEHAVIOR_STATE : 's',
/**
* Sends a "Connect.Unsafe.{method}" Arbiter message to facebook using the
* most efficient transport available.
*/
inform: function(method, params, relation, https, behavior) {
// TODO(naitik) only enable for iframe page tabs for now
if (FB.Canvas.isTabIframe() ||
(FB._inPlugin && window.postMessage) ||
(!FB._inCanvas && FB.UA.mobile() && window.postMessage)) {
var msg = FB.JSON.stringify({
method: method,
params: params,
behavior: behavior || FB.Arbiter.BEHAVIOR_PERSISTENT });
if (window.postMessage) { // native postMessage
FB.XD.resolveRelation(relation || 'parent').postMessage(msg, '*');
return;
} else {
try {
window.opener.postMessage(msg); // IE vbscript NIX transport
return;
} catch (e) {}
}
}
// canvas_proxy.php works by directly calling JS function on the parent
// window of current window. As such, it has to same document.domain and
// protocol (https/http). We don't have a good way to determine the
// protocol of the parent window and have to use window.referrer to
// infer it.
// Question: why should https ever be passed as a parameter?
https |= (window != window.parent &&
document.referrer.indexOf('https:') === 0);
// fallback static file based transport
var url = (
FB.getDomain((https ? 'https_' : '') + 'staticfb', true) +
FB.Arbiter._canvasProxyUrl + '#' +
FB.QS.encode({
method: method,
params: FB.JSON.stringify(params || {}),
behavior: behavior || FB.Arbiter.BEHAVIOR_PERSISTENT,
relation: relation
})
);
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.canvas
* @requires fb.prelude
* fb.arbiter
* fb.array
* fb.xd
*/
/**
* 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,
/**
* Keeps track of the last size of each frame
*/
_lastSize: {},
/**
* Keeps track of the last size and data about the parent canvas page
*/
_pageInfo: {
clientWidth: 0,
clientHeight: 0,
scrollLeft: 0,
scrollTop: 0,
offsetLeft: 0,
offsetTop: 0
},
/**
* canvas iframe position within the parent page.
*
* calls appCallback with the fresh data from the parent frame
* returns data from previous call directly for semi-backwards compatibility
* with the previous API (which used polling)
* @return {Object} containing scrollLeft, scrollTop,
* clientWidth, clientHeight, offsetLeft, and offsetTop
*
*/
getPageInfo: function(appCallback) {
var relation = 'top.frames[' + window.name + ']';
var channelUrl = FB.XD.handler(function(data) {
for (var i in FB.Canvas._pageInfo) {
if (data[i]) {
FB.Canvas._pageInfo[i] = data[i] | 0;
}
}
appCallback && appCallback(FB.Canvas._pageInfo);
}, relation, true);
var params = {
channelUrl : channelUrl,
frame : window.name
};
FB.Arbiter.inform('getPageInfo', params, 'top');
},
/**
* Use in conjunction with the hideFlashCallback parameter to FB.init().
* Developers should use this function within their hideFlashCallback to hide
* the element as soon as possible. Since Facebook will
* call this function 200ms later, and the implementation may change, using it
* is the only way to guarantee forward compatibility.
*/
hideFlashElement: function(elem) {
elem.style.visibility = 'hidden';
},
/**
* Use in conjunction with FB.Canvas.hideFlashElement.
* Developers should use this function within their hideFlashCallback to show
* the element as soon as possible. Since Facebook will
* call this function 200ms later, and the implementation may change, using it
* is the only way to guarantee forward compatibility.
*/
showFlashElement: function(elem) {
elem.style.visibility = '';
},
_flashClassID: "CLSID:D27CDB6E-AE6D-11CF-96B8-444553540000",
/**
* By default, we hide any flash objects that have the wmode=default or
* wmode=window when they might occlude something. Developers can
* override this with options.hideFlashCallback.
*/
_hideFlashCallback: function(params) {
var candidates = window.document.getElementsByTagName('object');
for (var i = 0; i < candidates.length; i++) {
var elem = candidates[i];
if (elem.type.toLowerCase() != "application/x-shockwave-flash" &&
elem.classid.toUpperCase() != FB.Canvas._flashClassID) {
continue;
}
var good = false;
for (var j = 0; j < elem.childNodes.length; j++) {
if (elem.childNodes[j].nodeName.toLowerCase() == "param" &&
elem.childNodes[j].name.toLowerCase() == "wmode") {
if (elem.childNodes[j].value.toLowerCase() == "opaque" ||
elem.childNodes[j].value.toLowerCase() == "transparent") {
good = true;
}
}
}
if (!good) {
var rand = Math.random();
if (rand <= 1 / 1000) {
FB.api(FB._apiKey + '/occludespopups', 'post', {});
}
if (FB.Canvas._devHideFlashCallback) {
// For each flash element, we give the application 200ms to do
// something reasonable. In this scenario we assume that the flash
// object has inherited visibility, and restore to that afterward.
var wait_ms = 200;
var devArgs = {
state : params.state,
elem : elem
};
var fnToggle = FB.bind(function(devParams) {
if (devParams.state == 'opened') {
FB.Canvas.hideFlashElement(devParams.elem);
} else {
FB.Canvas.showFlashElement(devParams.elem);
}
},
this,
devArgs);
setTimeout(fnToggle, wait_ms);
FB.Canvas._devHideFlashCallback(devArgs);
} else {
if (params.state == 'opened') {
elem._old_visibility = elem.style.visibility;
elem.style.visibility = 'hidden';
} else if (params.state == 'closed') {
elem.style.visibility = elem._old_visibility;
delete elem._old_visibility;
}
}
}
}
},
_devHideFlashCallback : null,
_setHideFlashCallback: function(callback) {
FB.Canvas._devHideFlashCallback = callback;
},
init: function() {
var view = FB.Dom.getViewportInfo();
FB.Canvas._pageInfo.clientWidth = view.width;
FB.Canvas._pageInfo.clientHeight = view.height;
FB.Canvas.getPageInfo();
var channelUrl = FB.XD.handler(
FB.Canvas._hideFlashCallback, 'top.frames[' + window.name + ']', true);
// Flash objects which are wmode=window (default) have an infinite Z-order.
// Canvas chrome needs to know so it can hide the iframe when various
// elements pop up.
FB.Arbiter.inform(
'iframeSetupFlashHiding', {channelUrl: channelUrl});
},
/**
* Tells Facebook to resize your iframe.
*
* ## 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 = {};
}
var minShrink = 0,
minGrow = 0;
params = params || {};
if (params.width == null || params.height == null) {
params = FB.copy(params, FB.Canvas._computeContentSize());
// we add a bit of hysteresis to the height check since the values
// returned from _computeContentSize() may be slightly different
// than the size we set the IFrame to so we need to avoid getting
// into a state where the IFrame keeps resizing slightly larger
// or ping ponging in size
minShrink = 16;
minGrow = 4;
}
params = FB.copy(params, { frame: window.name || 'iframe_canvas' });
// Deep compare
if (FB.Canvas._lastSize[params.frame]) {
var oldHeight = FB.Canvas._lastSize[params.frame].height;
var dHeight = params.height - oldHeight;
if (FB.Canvas._lastSize[params.frame].width == params.width &&
(dHeight <= minGrow && dHeight >= -minShrink)) {
return false;
}
}
FB.Canvas._lastSize[params.frame] = params;
FB.Arbiter.inform('setSize', params);
return true;
},
/**
* Tells Facebook to scroll your iframe.
*
* ## Examples
*
* Call this whenever you need to scroll the contents of your iframe. This
* will cause a setScroll to be executed on the containing iframe.
*
* @param {Integer} x
* @param {Integer} y
*
* Property | Type | Description | Argument | Default
* -------- | ------- | -------------------------------- | ---------- | -------
* x | Integer | Horizontal scroll position | *Required* | None
* y | Integer | Vertical scroll position | *Required* | None
*
* @author gregschechte
*/
scrollTo: function(x, y) {
FB.Arbiter.inform('scrollTo', {
frame: window.name || 'iframe_canvas',
x: x,
y: y
});
},
/**
* Starts or stops a timer which resizes your iframe every few milliseconds.
*
* ## 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.setAutoGrow();
* }
*
* If you ever need to stop the timer, just pass false.
*
* FB.Canvas.setAutoGrow(false);
*
* If you want the timer to run at a different interval, you can do that too.
*
* FB.Canvas.setAutoGrow(91); // Paul's favorite 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
*/
setAutoGrow: 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;
}
}
},
/**
* @deprecated use setAutoGrow()
*/
setAutoResize: function(onOrOff, interval) {
return FB.Canvas.setAutoGrow(onOrOff, interval);
},
/**
* The "app_runner_" pattern is set by facebook.com when embeding an
* application iframe(for now, only actually used on page tabs).
* If we detect this pattern, we can safely assume the
* parent frame will be able to handle async style ui calls.
* @return {Boolean} as explained above
*/
isTabIframe: function() {
return (window.name.indexOf('app_runner_') === 0);
},
/**
* This method should be called when your app is finished loading to the point
* when the user can use it.
* Pass in a callback which receives a struct like so:
* { time_delta_ms: 2346 }
* Which is the number of milliseconds between the moment the full canvas
* page began executing and when you called the function.
* This information will then be logged for Facebook Insights.
*/
setDoneLoading : function(callback) {
FB.Canvas._passAppTtiMessage(callback, 'RecordIframeAppTti');
},
/**
* When using FB.Canvas.setDoneLoading, this method can be called before
* periods of time that should not be measured, such as waiting for a user to
* click a button.
* Pass in a callback which receives a struct like so:
* { time_delta_ms: 2346 }
* Which is the number of milliseconds between the moment the full canvas
* page began executing and when you called the function.
*/
stopTimer : function(callback) {
FB.Canvas._passAppTtiMessage(callback, 'StopIframeAppTtiTimer');
},
/**
* This method can be called to register a callback for inline processing
* of user actions, such as clicks on OG action ticker stories.
* For instance, if user uses your app and clicks on achievement action,
* you can process it without reloading the page.
*
* Each call to setUrlHandler removes previously set callback, if there
* was one.
*
* @param {Function} callback function taking one argument: an object,
* field of which will be 'path' - the path relative to app's canvas URL;
* for instance, if the URL that would have been loaded was
* http://apps.facebook.com/app/achievement1.php?fb_rel=canvas_ticker...
* then callback will get {path: "/achievement1.php?fb_rel=canvas_ti..."}
*
* ## Example
*
* function onUrl(data) {
* if(data.path.indexOf("games.achieves") != -1) {
* console.log('I will process some achievement now.');
* } else {
* window.location = data.path;
* }
* }
*
* FB.Canvas.setUrlHandler(onUrl);
*/
setUrlHandler : function(callback) {
var channelUrl = FB.XD.handler(callback,
'top.frames[' + window.name + ']',
true);
FB.Arbiter.inform('setUrlHandler', channelUrl);
FB.Event.listen(window, 'load', function() {
FB.Arbiter.inform('setUrlHandler', channelUrl);
});
},
/**
* When using FB.Canvas.setDoneLoading, this method can be called after
* periods of time that should not be measured, such as after a user clicks a
* button.
*/
startTimer : function() {
FB.Canvas._passAppTtiMessage(null, 'StartIframeAppTtiTimer');
},
_passAppTtiMessage : function(callback, message_name) {
var devCallback = null;
if (callback) {
devCallback = FB.XD.handler(callback,
'top.frames[' + window.name + ']', false);
}
FB.Arbiter.inform(message_name,
{ frame: window.name || 'iframe_canvas',
time: (new Date()).getTime(),
appId: parseInt(FB._apiKey, 10),
channelUrl: devCallback
});
},
/**
* Determine the size of the actual contents of the iframe.
*
* There is no reliable way to get the height when the content is
* smaller than the IFrame in all browsers for all css.
* From measuring here's what works:
* CSS pos: default relative absolute fixed
* Webkit G+S G+S G x
* Firefox G+S G G x
* IE G G G x
*
* The only safe thing we can do is grow.
*
* Here's measured results from a test app. While it looks like we
* ought to be able to use body.offsetHeight, it turns out there are
* cases where apps with complex css are reported as much smaller
* than they actually render.
*
* content > IFrame=800
* body docElement jQuery .height()
* scroll offset scroll offset body doc
* chrome: 1838 1799 1834 1838 800 1838
* safari: 1838 1799 1834 1838 800 1838
* firefo: 1863 1863 1903 1903 800 1903
* ie7 : 2038 2038 2055 800 800 2055
* ie8 : 1850 1850 1890 800 800 1890
* ie9 : 1836 1820 1861 800 800 1861
* opera : 1850 1850 11890 1890
*
* content < IFrame=800
* body docElement jQuery .height()
* scroll offset scroll offset body doc
* chrome: 800 439 474 478 800 800
* safari: 800 439 474 478 800 800
* firefo: 455 455 798 493 800 800
* ie7 : 518 518 535 800 800 800
* ie8 : 450 450 800 800 800 800
* ie9 : 460 444 800 800 800 800
* opera : 450 450 10490 490
*
* Patches and test cases are welcome.
*/
_computeContentSize: function() {
var body = document.body,
docElement = document.documentElement,
right = 0,
bodyTop = Math.max(body.offsetTop, 0),
docTop = Math.max(docElement.offsetTop, 0),
bodyScroll = body.scrollHeight + bodyTop,
bodyOffset = body.offsetHeight + bodyTop,
docScroll = docElement.scrollHeight + docTop,
docOffset = docElement.offsetHeight + docTop;
bottom = Math.max(bodyScroll, bodyOffset, docScroll, docOffset);
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};
}
});
/**
* 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.string
* @layer basic
* @requires fb.prelude
*
*/
/**
* Utility function related to Strings.
*
* @class FB.String
* @static
* @private
*/
FB.provide('String', {
/**
* Strip leading and trailing whitespace.
*
* @param s {String} the string to trim
* @returns {String} the trimmed string
*/
trim: function(s) {
return s.replace(/^\s*|\s*$/g, '');
},
/**
* Format a string.
*
* Example:
* FB.String.format('{0}.facebook.com/{1}', 'www', 'login.php')
* Returns:
* 'www.facebook.com/login.php'
*
* Example:
* FB.String.format('foo {0}, {1}, {0}', 'x', 'y')
* Returns:
* 'foo x, y, x'
*
* @static
* @param format {String} the format specifier
* @param arguments {...} placeholder arguments
* @returns {String} the formatted string
*/
format: function(format) {
if (!FB.String.format._formatRE) {
FB.String.format._formatRE = /(\{[^\}^\{]+\})/g;
}
var values = arguments;
return format.replace(
FB.String.format._formatRE,
function(str, m) {
var
index = parseInt(m.substr(1), 10),
value = values[index + 1];
if (value === null || value === undefined) {
return '';
}
return value.toString();
}
);
},
/**
* Escape a string to safely use it as HTML.
*
* @param value {String} string to escape
* @return {String} the escaped string
*/
escapeHTML: function(value) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(value));
return div.innerHTML.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
},
/**
* Escape a string so that it can be embedded inside another string
* as quoted string.
*
* @param value {String} string to quote
* @return {String} the quoted string
*/
quote: function(value) {
var
quotes = /["\\\x00-\x1f\x7f-\x9f]/g,
subst = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
return quotes.test(value) ?
'"' + value.replace(quotes, function (a) {
var c = subst[a];
if (c) {
return c;
}
c = a.charCodeAt();
return '\\u00' + Math.floor(c/16).toString(16) + (c % 16).toString(16);
}) + '"' :
'"' + value + '"';
}
});
/**
* 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.dom
* @layer basic
* @requires fb.prelude
* fb.event
* fb.string
* fb.array
* fb.ua
*/
/**
* This provides helper methods related to DOM.
*
* @class FB.Dom
* @static
* @private
*/
FB.provide('Dom', {
/**
* Check if the element contains a class name.
*
* @param dom {DOMElement} the element
* @param className {String} the class name
* @return {Boolean}
*/
containsCss: function(dom, className) {
var cssClassWithSpace = ' ' + dom.className + ' ';
return cssClassWithSpace.indexOf(' ' + className + ' ') >= 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
}
},
/**
* Finds elements that have a certain class name
* A wrapper around document.querySelectorAll if
* supported, otherwise loops through all dom elements of given tagName
* hunting for the className.
*
* @param {String} className Class name we're interested in
* @param {HTMLElement} dom (optional) Element to search in
* @param {String} tagName (optional) Type of tag to look for, default "*"
* @return {Array}
*/
getByClass: function(className, dom, tagName) {
dom = dom || document.body;
tagName = tagName || '*';
if (dom.querySelectorAll) {
return FB.Array.toArray(
dom.querySelectorAll(tagName + '.' + className)
);
}
var all = dom.getElementsByTagName(tagName),
els = [];
for (var i = 0, len = all.length; i < len; i++) {
if (this.containsCss(all[i], className)) {
els[els.length] = all[i];
}
}
return els;
},
/**
* 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 (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'; }
}
}
}
if (styleProp == 'opacity') {
if (dom.filters && dom.filters.alpha) {
return y;
}
return y * 100;
}
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 (dom.filters) {
if (dom.filters.alpha == undefined) {
dom.filter = "alpha(opacity=" + value + ")";
} else {
dom.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 <style> tag.
*
* @param styles {String} the styles
* @param names {Array} the component names that the styles represent
*/
addCssRules: function(styles, names) {
if (!FB.Dom._cssRules) {
FB.Dom._cssRules = {};
}
// note, we potentially re-include CSS if it comes with other CSS that we
// have previously not included.
var allIncluded = true;
FB.Array.forEach(names, function(id) {
if (!(id in FB.Dom._cssRules)) {
allIncluded = false;
FB.Dom._cssRules[id] = true;
}
});
if (allIncluded) {
return;
}
//#JSCOVERAGE_IF
if (!FB.UA.ie()) {
var style = document.createElement('style');
style.type = 'text/css';
style.textContent = styles;
document.getElementsByTagName('head')[0].appendChild(style);
} else {
try {
document.createStyleSheet().cssText = styles;
} catch (exc) {
// major problem on IE : You can only create 31 stylesheet objects with
// this method. We will have to add the styles into an existing
// stylesheet.
if (document.styleSheets[0]) {
document.styleSheets[0].cssText += styles;
}
}
}
},
/**
* Get the viewport info. Contains size and scroll offsets.
*
* @returns {Object} with the width and height
*/
getViewportInfo: function() {
// W3C compliant, or fallback to body
var root = (document.documentElement && document.compatMode == 'CSS1Compat')
? document.documentElement
: document.body;
return {
scrollTop : root.scrollTop,
scrollLeft : root.scrollLeft,
width : self.innerWidth ? self.innerWidth : root.clientWidth,
height : self.innerHeight ? self.innerHeight : root.clientHeight
};
},
/**
* Bind a function to be executed when the DOM is ready. It will be executed
* immediately if the DOM is already ready.
*
* @param {Function} the function to invoke when ready
*/
ready: function(fn) {
if (FB.Dom._isReady) {
fn && fn();
} else {
FB.Event.subscribe('dom.ready', fn);
}
},
/**
* Find where `node` is on the page
*
* @param {DOMElement} the element
* @return {Object} with properties x and y
*/
getPosition: function(node) {
var x = 0,
y = 0;
do {
x += node.offsetLeft;
y += node.offsetTop;
} while (node = node.offsetParent);
return {x: x, y: y};
}
});
// NOTE: This code is self-executing. This is necessary in order to correctly
// determine the ready status.
(function() {
// Handle when the DOM is ready
function domReady() {
FB.Dom._isReady = true;
FB.Event.fire('dom.ready');
FB.Event.clear('dom.ready');
}
// In case we're already ready.
if (FB.Dom._isReady || document.readyState == 'complete') {
return domReady();
}
// Good citizens.
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', domReady, false);
// Bad citizens.
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', domReady);
}
// Bad citizens.
// If IE is used and page is not in a frame, continuously check to see if
// the document is ready
if (FB.UA.ie() && window === top) {
(function() {
try {
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
document.documentElement.doScroll('left');
} catch(error) {
setTimeout(arguments.callee, 0);
return;
}
// and execute any waiting functions
domReady();
})();
}
// Ultimate Fallback.
var oldonload = window.onload;
window.onload = function() {
domReady();
if (oldonload) {
if (typeof oldonload == 'string') {
eval(oldonload);
} else {
oldonload();
}
}
};
})();
/**
* 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', (function() {
/**
* Regular expression snippet containing all the characters that we
* count as sentence-final punctuation.
*/
var _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.
*/
function _endsInPunct(str) {
if (typeof str != 'string') {
return false;
}
return str.match(new RegExp(
_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
*/
function _substituteTokens(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 ' +
'FB.Intl.tx(' + str + ', ...)'
);
} else {
var regexp;
for (var key in args) {
if (args.hasOwnProperty(key)) {
// _substituteTokens("You are a {what}.", {what:'cow!'}) should be
// "You are a cow!" rather than "You are a cow!."
if (_endsInPunct(args[key])) {
// Replace both the token and the sentence-final punctuation
// after it, if any.
regexp = new RegExp('\{' + key + '\}' +
_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
*/
function tx(str, args) {
// 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 _substituteTokens(FB.Intl._stringTable[str], args);
}
// FB.Intl.tx('key') is rewritten to FB.Intl.tx._('Translated value')
tx._ = _substituteTokens;
return {
tx: tx,
// Temporary, for push safety. We are renaming _tx to tx._, and need this
// to allow users with the new JS to hit old servers.
_tx: _substituteTokens
};
})());
/**
* 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.
*
* @provides fb.obj
* @requires fb.type
* fb.json
* fb.event
*/
/**
* Base object type that support events.
*
* @class FB.Obj
* @private
*/
FB.Class('Obj', null,
FB.copy({
/**
* Set property on an object and fire property changed event if changed.
*
* @param {String} Property name. A event with the same name
* will be fire when the property is changed.
* @param {Object} new value of the property
* @private
*/
setProperty: function(name, value) {
// Check if property actually changed
if (FB.JSON.stringify(value) != FB.JSON.stringify(this[name])) {
this[name] = value;
this.fire(name, value);
}
}
}, FB.EventProvider)
);
/**
* @provides fb.dialog
* @requires fb.arbiter
* fb.array
* fb.content
* fb.dom
* fb.event
* fb.intl
* fb.obj
* fb.prelude
* fb.type
* fb.ua
* fb.xd
* @css fb.css.dialog
*/
/**
* 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.
*/
/**
* Dialog creation and management.
* To get an object, do
* var dialog = FB.ui(...);
* To subscribe to an event, do
* FB.dialog.subscribe(
* '<event name>', function() { alert("<event name> happened"); });
* This dialog may fire the following events
* 'iframe_hide' This event is fired if an iframe dialog is hidden but not
* closed. Note that the dialog may subsequently reopen, for example if
* there was an error.
* 'iframe_show' This event is fired when an iframe dialog is first shown, or
* when an error dialog is shown.
* @class FB.Dialog
* @public
*/
FB.subclass(
'Dialog',
'Obj',
/**
* constructor
* @param id
*/
function(id) {
this.id = id;
if (!FB.Dialog._dialogs) {
FB.Dialog._dialogs = {};
FB.Dialog._addOrientationHandler();
}
FB.Dialog._dialogs[id] = this;
},
// Members
{
}
);
FB.provide('Dialog', {
/**
*
*/
_dialogs: null,
_lastYOffset: 0,
/**
* The loader element.
*
* @access private
* @type DOMElement
*/
_loaderEl: null,
/**
* A la Snowbox overlay underneath the dialog on iPad.
*
* @access private
* @type DOMElement
*/
_overlayEl: null,
/**
* The stack of active dialogs.
*
* @access private
* @type Array
*/
_stack: [],
/**
* The currently visible dialog.
*
* @access private
* @type DOMElement
*/
_active: null,
/**
* The state of the popstate listener. Prevents multiple listeners from
* being created.
*
* @access private
* @type bool
*/
_popStateListenerOn: false,
/**
* Hides open dialog on popstate event
*
* @access private
*/
_hideOnPopState: function(e) {
FB.Dialog.hide(FB.Dialog._stack.pop());
},
/**
* Get dialog by id
* @access private
* @param id {string} dialog id
* @return {Dialog} a dialog object
*/
get: function(id) {
return FB.Dialog._dialogs[id];
},
/**
* Find the root dialog node for a given element. This will walk up the DOM
* tree and while a node exists it will check to see if has the fb_dialog
* class and if it does returns it.
*
* @access private
* @param node {DOMElement} a child node of the dialog
* @return {DOMElement} the root dialog element if found
*/
_findRoot: function(node) {
while (node) {
if (FB.Dom.containsCss(node, 'fb_dialog')) {
return node;
}
node = node.parentNode;
}
},
_createWWWLoader: function(width) {
width = parseInt(width, 10);
width = width ? width : 460;
return FB.Dialog.create({
content: (
'<div class="dialog_title">' +
' <a id="fb_dialog_loader_close">' +
' <div class="fb_dialog_close_icon"></div>' +
' </a>' +
' <span>Facebook</span>' +
' <div style="clear:both;"></div>' +
'</div>' +
'<div class="dialog_content"></div>' +
'<div class="dialog_footer"></div>'),
width: width
});
},
_createMobileLoader: function() {
// This chrome is native when possible.
// We're copying the HTML/CSS output of an XHP element here
// pretty much verbatim to easily keep them in sync.
// Copied from e.g. facebook.com/dialog/feed as rendered
// for a mobile user agent.
var chrome = FB.UA.nativeApp() ?
'' :
('<table>' +
' <tbody>' +
' <tr>' +
' <td class="header_left">' +
' <label class="touchable_button">' +
' <input type="submit" value="' +
FB.Intl.tx._("Cancel") + '"' +
' id="fb_dialog_loader_close"/>' +
' </label>' +
' </td>' +
' <td class="header_center">' +
' <div>' + FB.Intl.tx._("Loading...") + '</div>' +
' </td>' +
' <td class="header_right">' +
' </td>' +
' </tr>' +
' </tbody>' +
'</table>');
return FB.Dialog.create({
classes: 'loading' + (FB.UA.iPad() ? ' centered' : ''),
content: (
'<div class="dialog_header">' +
chrome +
'</div>')
});
},
_restoreBodyPosition: function() {
if (!FB.UA.iPad()) {
var body = document.getElementsByTagName('body')[0];
FB.Dom.removeCss(body, 'fb_hidden');
}
},
_showIPadOverlay: function() {
if (!FB.UA.iPad()) {
return;
}
if (!FB.Dialog._overlayEl) {
FB.Dialog._overlayEl = document.createElement('div');
FB.Dialog._overlayEl.setAttribute('id', 'fb_dialog_ipad_overlay');
FB.Content.append(FB.Dialog._overlayEl, null);
}
FB.Dialog._overlayEl.className = '';
},
_hideIPadOverlay: function() {
if (FB.UA.iPad()) {
FB.Dialog._overlayEl.className = 'hidden';
}
},
/**
* Show the "Loading..." dialog. This is a special dialog which does not
* follow the standard stacking semantics. If a callback is provided, a
* cancel action is provided using the "X" icon.
*
* @param cb {Function} optional callback for the "X" action
*/
showLoader: function(cb, width) {
FB.Dialog._showIPadOverlay();
if (!FB.Dialog._loaderEl) {
FB.Dialog._loaderEl = FB.Dialog._findRoot(
FB.UA.mobile()
? FB.Dialog._createMobileLoader()
: FB.Dialog._createWWWLoader(width));
}
// this needs to be done for each invocation of showLoader. since we don't
// stack loaders and instead simply hold on to the last one, it is possible
// that we are showing nothing when we can potentially be showing the
// loading dialog for a previously activated but not yet loaded dialog.
if (!cb) {
cb = function() {};
}
var loaderClose = FB.$('fb_dialog_loader_close');
FB.Dom.removeCss(loaderClose, 'fb_hidden');
loaderClose.onclick = function() {
FB.Dialog._hideLoader();
FB.Dialog._restoreBodyPosition();
FB.Dialog._hideIPadOverlay();
cb();
};
var iPadOverlay = FB.$('fb_dialog_ipad_overlay');
if (iPadOverlay) {
iPadOverlay.ontouchstart = loaderClose.onclick;
}
FB.Dialog._makeActive(FB.Dialog._loaderEl);
},
/**
* Hide the loading dialog if one is being shown.
*
* @access private
*/
_hideLoader: function() {
if (FB.Dialog._loaderEl && FB.Dialog._loaderEl == FB.Dialog._active) {
FB.Dialog._loaderEl.style.top = '-10000px';
}
},
/**
* Center a dialog based on its current dimensions and place it in the
* visible viewport area.
*
* @access private
* @param el {DOMElement} the dialog node
*/
_makeActive: function(el) {
FB.Dialog._setDialogSizes();
FB.Dialog._lowerActive();
FB.Dialog._active = el;
if (FB.Canvas) {
FB.Canvas.getPageInfo(function(pageInfo) {
FB.Dialog._centerActive(pageInfo);
});
}
// use the cached version of the pageInfo if slow or failed arbiter
// or not in canvas
FB.Dialog._centerActive(FB.Canvas._pageInfo);
},
/**
* Lower the current active dialog if there is one.
*
* @access private
* @param node {DOMElement} the dialog node
*/
_lowerActive: function() {
if (!FB.Dialog._active) {
return;
}
FB.Dialog._active.style.top = '-10000px';
FB.Dialog._active = null;
},
/**
* Remove the dialog from the stack.
*
* @access private
* @param node {DOMElement} the dialog node
*/
_removeStacked: function(dialog) {
FB.Dialog._stack = FB.Array.filter(FB.Dialog._stack, function(node) {
return node != dialog;
});
},
/**
* Centers the active dialog vertically.
*
* @access private
*/
_centerActive: function(pageInfo) {
var dialog = FB.Dialog._active;
if (!dialog) {
return;
}
var view = FB.Dom.getViewportInfo();
var width = parseInt(dialog.offsetWidth, 10);
var height = parseInt(dialog.offsetHeight, 10);
var left = view.scrollLeft + (view.width - width) / 2;
// Minimum and maximum values for the top of the dialog;
// these ensure that the dialog is always within the iframe's
// dimensions, with some padding.
// @todo(nikolay): When we refactor this module to avoid
// the excessive use of if (FB.UA.mobile()), get rid of
// this undesirable padding. It only looks bad on Desktop Safari
// (because of the scrollbars).
var minTop = (view.height - height) / 2.5;
if (left < minTop) {
minTop = left;
}
var maxTop = view.height - height - minTop;
// center vertically within the page
var top = (view.height - height) / 2;
if (pageInfo) {
top = pageInfo.scrollTop - pageInfo.offsetTop +
(pageInfo.clientHeight - height) / 2;
}
// clamp to min and max
if (top < minTop) {
top = minTop;
} else if (top > maxTop) {
top = maxTop;
}
// offset by the iframe's scroll
top += view.scrollTop;
// The body element is hidden at -10000px while we
// display dialogs. Full-screen on iPhone.
if (FB.UA.mobile()) {
// On mobile device (such as iPhone and iPad) that uses soft keyboard,
// when a text field has focus and the keyboard is shown, the OS will
// scroll a page to position the text field at the center of the remaining
// space. If page doesn't have enough height, then OS will effectively
// pull the page up by force while the keyboard is up, but the page will
// slide down as soon as the keyboard is hidden.
// When that happens, it can cause problems. For example, we had a nasty
// problem with typeahead control in app request dialog. When user types
// something in the control, the keyboard is up. However, when the user
// tap a selection, the keyboard disappears. If the page starts to scroll
// down, then the "click" event may fire from a differnt DOM element and
// cause wrong item (or no item) to be selected.
//
// After a lot of hacking around, the best solution we found is to insert
// an extra vertical padding element to give the page some extra space
// such that page won't be forced to scroll beyeond its limit when
// the text field inside the dialog needs to be centered. The negative
// side effect of this hack is that there will be some extra space
// that the user could scroll to.
var paddingHeight = 100;
// Smaller and centered on iPad. This should only run when the
// dialog is first rendered or the device rotated.
if (FB.UA.iPad()) {
paddingHeight += (view.height - height) / 2;
} else {
var body = document.getElementsByTagName('body')[0];
FB.Dom.addCss(body, 'fb_hidden');
left = 10000;
top = 10000;
}
var paddingDivs = FB.Dom.getByClass('fb_dialog_padding', dialog);
if (paddingDivs.length) {
paddingDivs[0].style.height = paddingHeight + 'px';
}
}
dialog.style.left = (left > 0 ? left : 0) + 'px';
dialog.style.top = (top > 0 ? top : 0) + 'px';
},
_setDialogSizes: function() {
if (!FB.UA.mobile() || FB.UA.iPad()) {
return;
}
for (var id in FB.Dialog._dialogs) {
if (document.getElementById(id)) {
var iframe = document.getElementById(id);
iframe.style.width = FB.UIServer.getDefaultSize().width + 'px';
iframe.style.height = FB.UIServer.getDefaultSize().height + 'px';
}
}
},
/**
* This adapt the position and orientation of the dialogs.
*/
_handleOrientationChange: function(e) {
// Normally on Android, screen.availWidth/availHeight/width/height reflect
// values corresponding to the current orientation. In other words,
// width/height changes depending on orientation. However,
// on Android 2.3 browser, the values do not change at the time of the
// "orientation" event, but change shortly after (50-150ms later).
//
// This behavior is annoying. I now have to work around it by doing a
// timer pulling in the orientation event to detect the correct
// screen.availWidth/height now.
if (FB.UA.android() && screen.availWidth == FB.Dialog._availScreenWidth) {
window.setTimeout(FB.Dialog._handleOrientationChange, 50);
return;
}
FB.Dialog._availScreenWidth = screen.availWidth;
if (FB.UA.iPad()) {
FB.Dialog._centerActive();
} else {
for (var id in FB.Dialog._dialogs) {
// Resize the width of any iframes still on the page
if (document.getElementById(id)) {
document.getElementById(id).style.width =
FB.UIServer.getDefaultSize().width + 'px';
}
}
}
},
/**
* Add some logic to fire on orientation change.
*/
_addOrientationHandler: function() {
if (!FB.UA.mobile()) {
return;
}
// onOrientationChange is fired on iOS and some Android devices,
// while other Android devices fire resize. Still other Android devices
// seem to fire neither.
var event_name = "onorientationchange" in window ?
'orientationchange' :
'resize';
FB.Dialog._availScreenWidth = screen.availWidth;
FB.Event.listen(window, event_name, FB.Dialog._handleOrientationChange);
},
/**
* Create a dialog. Returns the node of the dialog within which the caller
* can inject markup. Optional HTML string or a DOMElement can be passed in
* to be set as the content. Note, the dialog is hidden by default.
*
* @access protected
* @param opts {Object} Options:
* Property | Type | Description | Default
* --------- | ----------------- | --------------------------------- | -------
* content | String|DOMElement | HTML String or DOMElement |
* onClose | Boolean | callback if closed |
* closeIcon | Boolean | `true` to show close icon | `false`
* visible | Boolean | `true` to make visible | `false`
* width | Int | width of dialog | 'auto'
* classes | String | added to the dialog's classes | ''
*
* @return {DOMElement} the dialog content root
*/
create: function(opts) {
opts = opts || {};
var
dialog = document.createElement('div'),
contentRoot = document.createElement('div'),
className = 'fb_dialog';
// optional close icon
if (opts.closeIcon && opts.onClose) {
var closeIcon = document.createElement('a');
closeIcon.className = 'fb_dialog_close_icon';
closeIcon.onclick = opts.onClose;
dialog.appendChild(closeIcon);
}
className += ' ' + (opts.classes || '');
// handle rounded corners j0nx
//#JSCOVERAGE_IF
if (FB.UA.ie()) {
className += ' fb_dialog_legacy';
FB.Array.forEach(
[
'vert_left',
'vert_right',
'horiz_top',
'horiz_bottom',
'top_left',
'top_right',
'bottom_left',
'bottom_right'
],
function(name) {
var span = document.createElement('span');
span.className = 'fb_dialog_' + name;
dialog.appendChild(span);
}
);
} else {
className += (FB.UA.mobile())
? ' fb_dialog_mobile'
: ' fb_dialog_advanced';
}
if (opts.content) {
FB.Content.append(opts.content, contentRoot);
}
dialog.className = className;
var width = parseInt(opts.width, 10);
if (!isNaN(width)) {
dialog.style.width = width + 'px';
}
contentRoot.className = 'fb_dialog_content';
dialog.appendChild(contentRoot);
if (FB.UA.mobile()) {
var padding = document.createElement('div');
padding.className = 'fb_dialog_padding';
dialog.appendChild(padding);
}
FB.Content.append(dialog);
if (opts.visible) {
FB.Dialog.show(dialog);
}
return contentRoot;
},
/**
* Raises the given iframe dialog. Any active dialogs are automatically
* lowered. An active loading indicator is suppressed. An already-lowered
* dialog will be raised and it will be put at the top of the stack. A dialog
* never shown before will be added to the top of the stack.
*
* @access protected
* @param dialog {DOMElement} a child element of the dialog
*/
show: function(dialog) {
var root = FB.Dialog._findRoot(dialog);
if (root) {
FB.Dialog._removeStacked(root);
FB.Dialog._hideLoader();
FB.Dialog._makeActive(root);
FB.Dialog._stack.push(root);
if ('fbCallID' in dialog) {
FB.Dialog.get(dialog.fbCallID).fire('iframe_show');
}
if (!FB.Event._popStateListenerOn) {
FB.Event.listen(window, 'popstate', FB.Dialog._hideOnPopState);
FB.Event._popStateListenerOn = true;
}
}
},
/**
* Hide the given iframe dialog. The dialog will be lowered and moved out
* of view, but won't be removed.
*
* @access protected
* @param dialog {DOMElement} a child element of the dialog
*/
hide: function(dialog) {
var root = FB.Dialog._findRoot(dialog);
if (root == FB.Dialog._active) {
FB.Dialog._lowerActive();
FB.Dialog._restoreBodyPosition();
FB.Dialog._hideIPadOverlay();
if ('fbCallID' in dialog) {
FB.Dialog.get(dialog.fbCallID).fire('iframe_hide');
}
if (FB.Event._popStateListenerOn) {
FB.Event.unlisten(window, 'popstate', FB.Dialog._hideOnPopState);
FB.Event._popStateListenerOn = false;
}
}
},
/**
* Remove the dialog, show any stacked dialogs.
*
* @access protected
* @param dialog {DOMElement} a child element of the dialog
*/
remove: function(dialog) {
dialog = FB.Dialog._findRoot(dialog);
if (dialog) {
var is_active = FB.Dialog._active == dialog;
FB.Dialog._removeStacked(dialog);
if (is_active) {
FB.Dialog._hideLoader();
if (FB.Dialog._stack.length > 0) {
FB.Dialog.show(FB.Dialog._stack.pop());
} else {
FB.Dialog._lowerActive();
FB.Dialog._restoreBodyPosition();
FB.Dialog._hideIPadOverlay();
}
} else if (FB.Dialog._active === null && FB.Dialog._stack.length > 0) {
FB.Dialog.show(FB.Dialog._stack.pop());
}
// wait before the actual removal because of race conditions with async
// flash crap. seriously, dont ever ask me about it.
// if we remove this without deferring, then in IE only, we'll get an
// uncatchable error with no line numbers, function names, or stack
// traces. the 3 second delay isn't a problem because the <div> is
// already hidden, it's just not removed from the DOM yet.
window.setTimeout(function() {
dialog.parentNode.removeChild(dialog);
}, 3000);
}
},
/**
* Whether a given node is contained within the active dialog's root
*
* @access public
* @param dialog {DOMElement} a child element of the dialog
*/
isActive: function(node) {
var root = FB.Dialog._findRoot(node);
return root && root === FB.Dialog._active;
}
});
/**
* 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
* @requires fb.prelude
* fb.canvas
* fb.content
* fb.dialog
* fb.qs
* fb.json
* fb.xd
* fb.arbiter
* fb.ua
*/
/**
* UI Calls.
*
* @class FB
* @static
* @access private
*/
FB.provide('', {
/**
* Method for triggering UI interaction with Facebook as iframe dialogs or
* popups, like publishing to the stream, sharing links.
*
* Example **stream.publish**:
*
* FB.ui(
* {
* method: 'stream.publish',
* message: 'getting educated about Facebook Connect',
* attachment: {
* name: 'Connect',
* caption: 'The Facebook Connect JavaScript SDK',
* description: (
* 'A small 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: 'Code', href: 'http://github.com/facebook/connect-js' }
* ],
* user_message_prompt: 'Share your thoughts about Connect'
* },
* function(response) {
* if (response && response.post_id) {
* alert('Post was published.');
* } else {
* alert('Post was not published.');
* }
* }
* );
*
* Example **stream.share**:
*
* var share = {
* method: 'stream.share',
* u: 'http://fbrell.com/'
* };
*
* FB.ui(share, function(response) { console.log(response); });
*
* @access public
* @param params {Object} The required arguments vary based on the method
* being used, but specifying the method itself is mandatory. If *display* is
* not specified, then iframe dialogs will be used when possible, and popups
* otherwise.
*
* Property | Type | Description | Argument
* -------- | ------- | ---------------------------------- | ------------
* method | String | The UI dialog to invoke. | **Required**
* display | String | Specify `"popup"` to force popups. | **Optional**
* @param cb {Function} Optional callback function to handle the result. Not
* all methods may have a response.
*/
ui: function(params, cb) {
params = FB.copy({}, params);
if (!params.method) {
FB.log('"method" is a required parameter for FB.ui().');
return null;
}
// CORDOVA PATCH
// If the nativeInterface arg is specified then call out to the nativeInterface
// which uses the native app rather than using the iframe / popup web
if (FB._nativeInterface) {
switch (params.method) {
case 'auth.login':
FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
break;
case 'permissions.request':
FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
break;
case 'permissions.oauth':
FB._nativeInterface.login(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on login!' + e);});
break;
case 'auth.logout':
FB._nativeInterface.logout(cb, function(e) {alert('Cordova Facebook Connect plugin fail on logout!');});
break;
case 'auth.status':
FB._nativeInterface.getLoginStatus(cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
break;
case 'login.status':
FB._nativeInterface.getLoginStatus(cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
break;
case 'feed':
FB._nativeInterface.dialog(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
break;
case 'apprequests':
FB._nativeInterface.dialog(params, cb, function(e) {alert('Cordova Facebook Connect plugin fail on auth.status!');});
break;
}
return;
}
// process popup-only permissions
if ((params.method == 'permissions.request' ||
params.method == 'permissions.oauth') &&
(params.display == 'iframe' || params.display == 'dialog')) {
var perms;
var requested_perms;
perms = params.scope;
requested_perms = perms.split(/\s|,/g);
// OAuth2 spec says scope should be space delimited, but
// we previously accepted comma delimited strings. We'll accept both.
for (var i = 0; i < requested_perms.length; i++) {
var perm = FB.String.trim(requested_perms[i]);
// force a popup if we are not in the whitelist or we're set as
// false explicitly (and if the perm value is nonempty)
if (perm && !FB.initSitevars.iframePermissions[perm]) {
params.display = 'popup';
// we call this recursively to reprocess the prepareCall logic
// and make sure we'll pass the right parameters.
break;
}
}
}
var call = FB.UIServer.prepareCall(params, cb);
if (!call) { // aborted
return null;
}
// each allowed "display" value maps to a function
var displayName = call.params.display;
if (displayName === 'dialog') { // TODO remove once all dialogs are on
// uiserver
displayName = 'iframe';
} else if (displayName === 'none') {
displayName = 'hidden';
}
var displayFn = FB.UIServer[displayName];
if (!displayFn) {
FB.log('"display" must be one of "popup", ' +
'"dialog", "iframe", "touch", "async", "hidden", or "none"');
return null;
}
displayFn(call);
return call.dialog;
}
});
/**
* Internal UI functions.
*
* @class FB.UIServer
* @static
* @access private
*/
FB.provide('UIServer', {
/**
* UI Methods will be defined in this namespace.
*/
Methods: {},
// Child iframes or popup windows.
_loadedNodes : {},
_defaultCb : {},
_resultToken : '"xxRESULTTOKENxx"',
_forceHTTPS : false,
/**
* Serves as a generic transform for UI Server dialogs. Once all dialogs are
* built on UI Server, this will just become the default behavior.
*
* Current transforms:
* 1) display=dialog -> 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.display = 'iframe';
call.params.channel = FB.UIServer._xdChannelHandler(
call.id,
'parent.parent'
);
}
return call;
},
/**
* Prepares a generic UI call. Some regular API call also go through
* here though via hidden iframes.
*
* @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
name = params.method.toLowerCase(),
method = FB.copy({}, FB.UIServer.Methods[name]),
id = FB.guid(),
// TODO(naitik) don't want to force login status over HTTPS just yet. all
// other UI Server interactions will be forced over HTTPS,
// Methods can choose to not use https by setting noHttps=true
forceHTTPS = (method.noHttps !== true) &&
(FB._https ||
(name !== 'auth.status' && name != 'login.status'));
FB.UIServer._forceHTTPS = forceHTTPS;
// default stuff
FB.copy(params, {
api_key : FB._apiKey,
app_id : FB._apiKey,
locale : FB._locale,
sdk : 'joey',
access_token : forceHTTPS && FB.getAccessToken() || undefined
});
// overwrite display based on final param set
params.display = FB.UIServer.getDisplayMode(method, params);
// set the default dialog URL if one doesn't exist
if (!method.url) {
method.url = 'dialog/' + name;
}
// the basic call data
var call = {
cb : cb,
id : id,
size : method.size || FB.UIServer.getDefaultSize(),
url : FB.getDomain(forceHTTPS ? 'https_www' : 'www') + method.url,
forceHTTPS: forceHTTPS,
params : params,
name : name,
dialog : new FB.Dialog(id)
};
// optional method transform
var transform = method.transform
? method.transform
: FB.UIServer.genericTransform;
if (transform) {
call = 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 getXdRelationFn = method.getXdRelation || FB.UIServer.getXdRelation;
var relation = getXdRelationFn(call.params);
if (!(call.id in FB.UIServer._defaultCb) &&
!('next' in call.params) &&
!('redirect_uri' 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'
);
}
// Encode the params as a query string or in the fragment
call = FB.UIServer.prepareParams(call);
return call;
},
prepareParams: function(call) {
var method = call.params.method;
// Page iframes still hit /fbml/ajax/uiserver.php
// which uses the old method names.
// On the other hand, the new endpoint might not expect
// the method as a param.
if (!FB.Canvas.isTabIframe()) {
delete call.params.method;
}
if (FB.TemplateUI && FB.TemplateUI.supportsTemplate(method, call)) {
// Temporary debug info.
if (FB.reportTemplates) {
console.log("Using template for " + method + ".");
}
FB.TemplateUI.useCachedUI(method, call);
} else {
// flatten parameters as needed
call.params = FB.JSON.flatten(call.params);
var encodedQS = FB.QS.encode(call.params);
// To overcome the QS length limitation on some browsers
// (the fb native app is an exception because it doesn't
// doesn't support POST for dialogs).
if (!FB.UA.nativeApp() &&
FB.UIServer.urlTooLongForIE(call.url + '?' + encodedQS)) {
call.post = true;
} else if (encodedQS) {
call.url += '?' + encodedQS;
}
}
return call;
},
urlTooLongForIE: function(fullURL) {
return fullURL.length > 2000;
},
/**
* Determine the display mode for the call.
*
* @param method {Object} the method definition object
* @param params {Object} the developer supplied parameters
* @return {String} the display mode
*/
getDisplayMode: function(method, params) {
if (params.display === 'hidden' ||
params.display === 'none') {
return params.display;
}
if (FB.Canvas.isTabIframe() &&
params.display !== 'popup') {
return 'async';
}
// For mobile, we should use touch display mode
if (FB.UA.mobile() || params.display === 'touch') {
return 'touch';
}
// cannot use an iframe "dialog" if an access token is not available
if (!FB.getAccessToken() &&
params.display == 'dialog' &&
!method.loggedOutIframe) {
FB.log('"dialog" mode can only be used when the user is connected.');
return 'popup';
}
if (method.connectDisplay && !FB._inCanvas) {
return method.connectDisplay;
}
// TODO change "dialog" to "iframe" once moved to uiserver
return params.display || (FB.getAccessToken() ? 'dialog' : 'popup');
},
/**
* Determine the frame relation for given params
*
* @param params {Object} the call params
* @return {String} the relation string
*/
getXdRelation: function(params) {
var display = params.display;
if (display === 'popup' || display === 'touch') {
return 'opener';
}
if (display === 'dialog' || display === 'iframe' ||
display === 'hidden' || display === 'none') {
return 'parent';
}
if (display === 'async') {
return 'parent.frames[' + window.name + ']';
}
},
/**
* 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
// Mobile popups should never specify width/height features since it
// messes with the dimension styles of the page layout.
width = FB.UA.mobile() ? null : call.size.width,
height = FB.UA.mobile() ? null : call.size.height,
screenX = (_screenX < 0) ? window.screen.width + _screenX : _screenX,
left = parseInt(screenX + ((outerWidth - width) / 2), 10),
top = parseInt(screenY + ((outerHeight - height) / 2.5), 10),
features = [];
if (width !== null) {
features.push('width=' + width);
}
if (height !== null) {
features.push('height=' + height);
}
features.push('left=' + left);
features.push('top=' + top);
features.push('scrollbars=1');
if (call.name == 'permissions.request' ||
call.name == 'permissions.oauth') {
features.push('location=1,toolbar=0');
}
features = features.join(',');
// either a empty window and then a POST, or a direct GET to the full url
if (call.post) {
FB.UIServer.setLoadedNode(call,
window.open('about:blank', call.id, features), 'popup');
FB.Content.submitToTarget({
url : call.url,
target : call.id,
params : call.params
});
} else {
FB.UIServer.setLoadedNode(call,
window.open(call.url, call.id, features), 'popup');
}
// if there's a default close action, setup the monitor for it
if (call.id in FB.UIServer._defaultCb) {
FB.UIServer._popupMonitor();
}
},
setLoadedNode: function(call, node, type) {
if (call.params && call.params.display != 'popup') {
// Note that we avoid setting fbCallID property on node when
// display is popup because when the page is loaded via http,
// you can't set a property on an https popup window in IE.
node.fbCallID = call.id;
}
node = {
node: node,
type: type,
fbCallID: call.id
};
FB.UIServer._loadedNodes[call.id] = node;
},
getLoadedNode: function(call) {
var id = typeof call == 'object' ? call.id : call,
node = FB.UIServer._loadedNodes[id];
return node ? node.node : null;
},
/**
* 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';
var onClose = function() {
FB.UIServer._triggerDefault(call.id);
};
call.root = FB.Dialog.create({
onClose: onClose,
closeIcon: true,
classes: (FB.UA.iPad() ? 'centered' : '')
});
if (!call.hideLoader) {
FB.Dialog.showLoader(onClose, call.size.width);
}
FB.Dom.addCss(call.root, 'fb_dialog_iframe');
FB.UIServer._insertIframe(call);
},
/**
* Display an overlay dialog on a mobile device. This works both in the native
* mobile canvas frame as well as a regular mobile web browser.
*
* @access private
* @param call {Object} the call data
*/
touch: function(call) {
if (call.params && call.params.in_iframe) {
// Cached dialog was already created. Still show loader while it runs
// JS to adapt its content to the FB.ui params.
if (call.ui_created) {
FB.Dialog.showLoader(function() {
FB.UIServer._triggerDefault(call.id);
}, 0);
} else {
FB.UIServer.iframe(call);
}
} else if (FB.UA.nativeApp() && !call.ui_created) {
// When running inside native app, window.open is not supported.
// We need to create an webview using custom JS bridge function
call.frame = call.id;
FB.Native.onready(function() {
// TODO:
// We normally use window.name to pass cb token, but
// FB.Native.open doesn't accept a name parameter that it
// can pass to webview, so we use pass name through
// fragment for now. We should investigate to see if we can
// pass a window.name
FB.UIServer.setLoadedNode(call, FB.Native.open(
call.url + '#cb=' + call.frameName));
});
FB.UIServer._popupMonitor();
} else if (!call.ui_created) {
// Use popup by default
FB.UIServer.popup(call);
}
},
/**
* This is used when the application is running as a child iframe on
* facebook.com. This flow involves sending a message to the parent frame and
* asking it to render the UIServer dialog as part of the Facebook chrome.
*
* @access private
* @param call {Object} the call data
*/
async: function(call) {
call.frame = window.name;
delete call.url;
delete call.size;
FB.Arbiter.inform('showDialog', call);
},
getDefaultSize: function() {
if (FB.UA.mobile()) {
if (FB.UA.iPad()) {
return {
width: 500,
height: 590
};
} else if (FB.UA.android()) {
// Android browser needs special handling because
// window.innerWidth/Height doesn't return correct values
return {
width: screen.availWidth,
height: screen.availHeight
};
} else {
var width = window.innerWidth;
var height = window.innerHeight;
var isLandscape = width / height > 1.2;
// Make sure that the iframe width is not greater than screen width.
// We also start by calculating full screen height. In that case,
// window.innerHeight is not good enough because it doesn't take into
// account the height of address bar, etc. So we tried to use
// screen.width/height, but that alone is also not good enough because
// screen value is physical pixel value, but we need virtual pixel
// value because the virtual pixels value can be different from physical
// values depending on viewport meta tags.
// So in the end, we use the maximum value. It is OK
// if the height is too high because our new mobile dialog flow the
// content from top down.
return {
width: width,
height: Math.max(height,
(isLandscape ? screen.width : screen.height))
};
}
}
return {width: 575, height: 240};
},
/**
* Inserts an iframe based on the given call data.
*
* @access private
* @param call {Object} the call data
*/
_insertIframe: function(call) {
// the dialog may be cancelled even before we have a valid iframe node
// giving us a race condition. if this happens, the call.id will be removed
// from the _frames nodes, and we won't add the node back in.
FB.UIServer._loadedNodes[call.id] = false;
var activate = function(node) {
if (call.id in FB.UIServer._loadedNodes) {
FB.UIServer.setLoadedNode(call, node, 'iframe');
}
};
// 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,
id : call.id,
onInsert : activate,
onload : function(node) {
FB.Content.submitToTarget({
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,
id : call.id,
name : call.frameName,
onInsert : activate
});
}
},
/**
* @param frame {String} the id of the iframe being resized
* @param data {Object} data from the XD call it made
*
* @access private
*/
_handleResizeMessage: function(frame, data) {
var node = FB.UIServer.getLoadedNode(frame);
if (!node) {
return;
}
if (data.height) {
node.style.height = data.height + 'px';
}
if (data.width) {
node.style.width = data.width + 'px';
}
FB.Arbiter.inform(
'resize.ack',
data || {},
'parent.frames[' + node.name + ']',
true);
if (!FB.Dialog.isActive(node)) {
FB.Dialog.show(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._loadedNodes) {
// ignore prototype properties, and ones without a default callback
if (FB.UIServer._loadedNodes.hasOwnProperty(id) &&
id in FB.UIServer._defaultCb) {
var node = FB.UIServer._loadedNodes[id];
if (node.type != 'popup') {
continue;
}
win = node.node;
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 that do not kill the dialog or remove the handler.
* Terminating logic should be handled within 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) {
var forceHTTPS = (FB.UIServer._forceHTTPS &&
FB.UA.ie() !== 7);
return FB.XD.handler(function(data) {
var node = FB.UIServer.getLoadedNode(frame);
if (!node) { // dead handler
return;
}
if (data.type == 'resize') {
FB.UIServer._handleResizeMessage(frame, data);
} else if (data.type == 'hide') {
FB.Dialog.hide(node);
} else if (data.type == 'rendered') {
var root = FB.Dialog._findRoot(node);
FB.Dialog.show(root);
} else if (data.type == 'fireevent') {
FB.Event.fire(data.event);
}
}, relation, true, null, forceHTTPS);
},
/**
* 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.
* Once it is fired it is also deleted.
*
* @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.getLoadedNode(data.frame);
if (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 the 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);
if (FB.TemplateUI && FB.UA.mobile()) {
FB.TemplateUI.populateCache();
}
}
} 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._loadedNodes[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 &&
FB.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.auth
* @requires fb.prelude
* fb.qs
* fb.event
* fb.json
* fb.ui
* fb.ua
*/
/**
* Authentication and Authorization.
*
* @class FB
* @static
* @access private
*/
FB.provide('', {
/**
* Find out the current status from the server, and get an authResponse 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 an authResponse for them.
*
* FB.getLoginStatus(function(response) {
* if (response.authResponse) {
* FB.assert(response.status === 'connected');
* // logged in and connected user, someone you know
* } else if (response.status === 'not_authorized') {
* // the user is logged in but not connected to the application
* } else {
* FB.assert(response.status === 'unknown');
* // the user isn't even logged in to Facebook.
* }
* });
*
* **Events**
*
* #### auth.login
* This event is fired when your application first notices the user (in other
* words, gets an authResponse 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 an authResponse but can no longer
* validate the current user).
* #### auth.authResponseChange
* This event is fired for **any** auth related change as they all affect the
* access token: login, logout, and access token refresh. Access tokens are
* are refreshed over time as long as the user is active with your
* application.
* #### auth.statusChange
* Typically you will want to use the auth.authResponseChange 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`.
*
* authResponse
* : The authorization response. The field is presented if and only if the
* user is logged in and connected to your app.
*
* [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,
authResponse: FB._authResponse});
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 callbacks
var lsCb = function(response) {
// done
FB.Auth._loadState = 'loaded';
// invoke callbacks
FB.Event.fire('FB.loginStatus', response);
FB.Event.clear('FB.loginStatus');
};
FB.Auth.fetchLoginStatus(lsCb);
},
/**
* Returns the full packet of information about the user and
* his or her access token, or null if there's no active access
* token. This packet is referred to as the authorization response.
*
* @access public
* return {Object} a record containing the access token, then user id,
* the amount of time before it expires, and the
* signed request (or null if there's no active access token).
*/
getAuthResponse: function() {
return FB._authResponse;
},
/**
* Returns the access token embedded within the authResponse
* (or null if it's not available).
*
* @access public
* @return {String} the access token, if available, or null if not.
*/
getAccessToken: function() {
return (FB._authResponse && FB._authResponse.accessToken) || null;
},
/**
* Returns the ID of the connected user, or 0 if
* the user is logged out or otherwise couldn't be
* discerned from cookie or access token information.
*
* @access public
* @return {Integer} the ID of the logged in, connected user.
*/
getUserID: function() {
return FB._userID;
},
/**
* 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.authResponse) {
* // 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.authResponse) {
* // if you need to know which permissions were granted then
* // you can can make an fql-call
* FB.api({
* method: 'fql.query',
* query: 'select read_stream, publish_stream, ' +
* 'offline_access from permissions where uid=me()'
* },
* function (data) {
* if (data[0].read_stream) {
* // we have read_stream
* }
* });
* } else {
* // user is not logged in
* }
* }, {scope:'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
* ------------------------ | ------- | -------------------------------------
* 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.
* scope | String | Comma or space delimited list of
* | | [Extended permissions]
* | | (/docs/authentication/permissions).
*/
login: function(cb, opts) {
if (opts && opts.perms && !opts.scope) {
opts.scope = opts.perms;
delete opts.perms;
FB.log('OAuth2 specification states that \'perms\' ' +
'should now be called \'scope\'. Please update.');
}
FB.ui(FB.copy({
method: 'permissions.oauth',
display: 'popup',
domain: location.hostname
}, 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: [],
_xdStorePath: 'xd_localstorage/',
/**
* Fetch a fresh login status from the server. This should not ordinarily
* be called directly; use FB.getLoginStatus instead.
*/
fetchLoginStatus: function(lsCb) {
// CORDOVA PATCH
if (FB.UA.mobile() && window.postMessage && window.localStorage && !FB._nativeInterface) {
FB.Auth.staticAuthCheck(lsCb);
} else {
FB.ui({
method: 'login.status',
display: 'none',
domain: location.hostname
},
lsCb
);
}
},
/**
* Perform auth check using static endpoint first, then use
* login_status as backup when static endpoint does not fetch any
* results.
*/
staticAuthCheck: function(lsCb) {
var domain = FB.getDomain('https_staticfb');
FB.Content.insertIframe({
root: FB.Content.appendHidden(''),
className: 'FB_UI_Hidden',
url: domain + FB.Auth._xdStorePath,
onload: function(iframe) {
var server = frames[iframe.name];
var guid = FB.guid();
var handled = false;
var fn = function(response) {
if (!handled) {
handled = true;
FB.Auth._staticAuthHandler(lsCb, response);
}
};
FB.XD.handler(fn, 'parent', true, guid);
// In case the static handler doesn't respond in time, we use
// a timer to trigger a response.
setTimeout(fn, 500);
server.postMessage(
FB.JSON.stringify({
method: 'getItem',
params: ['LoginInfo_' + FB._apiKey, /* do_log */ true],
returnCb: guid
}),
domain);
}
});
},
_staticAuthHandler: function(cb, response) {
if (response && response.data && response.data.status &&
response.data.status == 'connected') {
var r;
var status = response.data.status;
if (response.data.https == 1) {
FB._https = true;
}
var authResponse = response.data.authResponse || null;
r = FB.Auth.setAuthResponse(authResponse, status);
cb && cb(r);
} else {
// finally make the call to login status
FB.ui({ method: 'login.status', display: 'none' }, cb);
}
},
/**
* Sets new access token and user status values. Invokes all the registered
* subscribers if needed.
*
* @access private
* @param authResponse {Object} the new auth response surrouning the access
* token, user id, signed request, and expiry
* time.
* @param status {String} the new status
* @return {Object} the "response" object, which is a simple
* dictionary object surrounding the two
* incoming values.
*/
setAuthResponse: function(authResponse, status) {
var userID = 0;
if (authResponse) {
// if there's an auth record, then there are a few ways we might
// actually get a user ID out of it. If an explcit user ID is provided,
// then go with that. If there's no explicit user ID, but there's a valid
// signed request with a user ID inside, then use that as a backup.
if (authResponse.userID) {
userID = authResponse.userID;
} else if (authResponse.signedRequest) {
var parsedSignedRequest =
FB.Auth.parseSignedRequest(authResponse.signedRequest);
if (parsedSignedRequest && parsedSignedRequest.user_id) {
userID = parsedSignedRequest.user_id;
}
}
}
var
login = !FB._userID && authResponse,
logout = FB._userID && !authResponse,
both = authResponse && FB._userID != userID,
authResponseChange = login || logout || both,
statusChange = status != FB._userStatus;
var response = {
authResponse : authResponse,
status : status
};
FB._authResponse = authResponse;
FB._userID = userID;
FB._userStatus = status;
if (logout || both) {
FB.Event.fire('auth.logout', response);
}
if (login || both) {
FB.Event.fire('auth.login', response);
}
if (authResponseChange) {
FB.Event.fire('auth.authResponseChange', response);
}
if (statusChange) {
FB.Event.fire('auth.statusChange', response);
}
// re-setup a timer to refresh the authResponse if needed. we only do this
// if FB.Auth._loadState exists, indicating that the application relies on
// the JS to get and refresh authResponse information
// (vs managing it themselves).
if (FB.Auth._refreshTimer) {
window.clearTimeout(FB.Auth._refreshTimer);
delete FB.Auth._refreshTimer;
}
if (FB.Auth._loadState && authResponse) {
FB.Auth._refreshTimer = window.setTimeout(function() {
FB.getLoginStatus(null, true); // force refresh
}, 1200000); // 20 minutes
}
return response;
},
_getContextType: function() {
// Set session origin
// WEB = 1
// MOBILE_CANVAS = 2
// NATIVE_MOBILE = 3
// DESKTOP = 4
// WEB_CANVAS = 5
if (FB.UA.nativeApp()) {
return 3;
}
if (FB.UA.mobile()) {
return 2;
}
if (FB._inCanvas) {
return 5;
}
return 1;
},
/**
* This handles receiving an access token from:
* - /dialog/oauth
*
* Whenever a user is logged in and has connected to the application, the
* params passed to the supplied callback include:
*
* {
* access_token: an access token
* expires_in: the number of seconds before the access token expires
* code: the authorization code used to generate
* signed_request: the code/user_id cookie, provided if and only if
* cookies are enabled.
* }
*
* If the user is logged out, or if the user is logged in and not connected,
* then the callback gets a smaller param record that includes:
*
* {
* error: either 'not_authorized' or 'unknown'
* }
*
* @access private
* @param cb {Function} the callback function.
* @param frame {String} the frame id the callback is tied to.
* @param target {String} 'parent' or 'opener' to indicate window
* relation.
* @param authResponse {Object} backup access token record, if not
* found in response.
* @param method {String} the name of the method invoking this
* @return {String} the xd url bound to the callback
*/
xdHandler: function(cb, frame, target, authResponse, method) {
return FB.UIServer._xdNextHandler(
FB.Auth.xdResponseWrapper(cb, authResponse, method),
frame,
target,
true);
},
/**
* This handles receiving an access token from:
* - /dialog/oauth
*
* It updates the internal SDK access token record based on the response
* and invokes the (optional) user specified callback.
*
* Whenever a user is logged in and has connected to the application, the
* callback gets the following passed to it:
*
* {
* access_token: an access token
* expires_in: the number of seconds before the access token expires
* code: the authorization code used to generate
* signed_request: the code/user_id cookie, provided if and only if
* cookies are enabled.
* }
*
* If the user is logged out, or if the user is logged in and not connected,
* then the callback gets a smaller param record that includes:
*
* {
* error: either 'not_authorized' or 'unknown'
* }
*
* @access private
* @param cb {Function} the callback function
* @param status {String} the connect status this handler will
* trigger
* @param authResponse {Object} backup access token record, if none
* is found in response
* @param method {String} the name of the method invoking this
* @return {Function} the wrapped xd handler function
*/
xdResponseWrapper: function(cb, authResponse, method) {
return function(params) {
if (params.access_token) {
// Whatever this is a response to, it succeeded
var parsedSignedRequest =
FB.Auth.parseSignedRequest(params.signed_request);
authResponse = {
accessToken: params.access_token,
userID: parsedSignedRequest.user_id,
expiresIn: parseInt(params.expires_in, 10),
signedRequest: params.signed_request
};
if (FB.Cookie.getEnabled()) {
var expirationTime = authResponse.expiresIn === 0
? 0 // make this a session cookie if it's for offline access
: (new Date()).getTime() + authResponse.expiresIn * 1000;
var baseDomain = FB.Cookie._domain;
if (!baseDomain && params.base_domain) {
// if no base domain was set, and we got a base domain back
// from the our side, lets use this and prepend . to also
// cover subdomains (this will actually be added anyway by
// the browser).
baseDomain = '.' + params.base_domain;
}
FB.Cookie.setSignedRequestCookie(params.signed_request,
expirationTime,
baseDomain);
}
FB.Auth.setAuthResponse(authResponse, 'connected');
} else if (!FB._authResponse && authResponse) {
// Should currently not be hit since authResponse is a copy of
// FB._authResponse
// use the cached version we had access to
FB.Auth.setAuthResponse(authResponse, 'connected');
} else if (!(authResponse && method == 'permissions.oauth')) {
// Do not enter this when we had an authResponse at the time
// of calling permissions.oauth, and no access_token was returned.
// This is the case when a TOSed app requests additional perms,
// but the user skips this.
var status;
if (params.error && params.error === 'not_authorized') {
status = 'not_authorized';
} else {
status = 'unknown';
}
FB.Auth.setAuthResponse(null, status);
if (FB.Cookie.getEnabled()) {
FB.Cookie.clearSignedRequestCookie();
}
}
// Use HTTPS for future requests.
if (params && params.https == 1 && !FB._https) {
FB._https = true;
}
response = {
authResponse: FB._authResponse,
status: FB._userStatus
};
cb && cb(response);
};
},
/**
* Discards the signature part of the signed request
* (we don't have the secret used to sign it, and we can't
* expect developers to expose their secret here), and
* base64URL-decodes and json-decodes the payload portion
* to return a small dictionary around the authorization code
* and user id.
*
* @return {Object} small JS object housing an authorization
* code and the user id.
*/
parseSignedRequest: function(signed_request) {
if (!signed_request) {
return null;
}
var boom = signed_request.split('.', 2);
// boom[0] is a signature that can't be verified here, because
// we don't (and shouldn't) have client side access to the app secret
var payload = boom[1];
var data = FB.Auth.base64URLDecode(payload);
return FB.JSON.parse(data);
},
/**
* Standard algorithm to decode a packet known to be encoded
* using the standard base64 encoding algorithm, save for the
* difference that the packet contains - where there would normally
* have been a +, and _ where there'd normally be a /.
*
* @param {String}
*/
base64URLDecode: function(input) {
// +'s and /'s are replaced, by Facebook, with urlencode-safe
// characters - and _, respectively. We could just changed the
// key string, but better to clarify this and then go with the
// standard key string, in case this code gets lifted and dropped
// somewhere else.
input = input.replace(/\-/g, '+').replace(/\_/g, '/');
// our signed requests aren't automatically 0 mod 4 in length, so we
// need to pad with some '=' characters to round it out.
if (input.length % 4 !== 0) {
var padding = 4 - input.length % 4;
for (var d = 0; d < padding; d++) {
input = input + '=';
}
}
var keyStr =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
for (var i = 0; i < input.length; i += 4) {
enc1 = keyStr.indexOf(input.charAt(i));
enc2 = keyStr.indexOf(input.charAt(i + 1));
enc3 = keyStr.indexOf(input.charAt(i + 2));
enc4 = keyStr.indexOf(input.charAt(i + 3));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
}
return unescape(output);
}
});
FB.provide('UIServer.Methods', {
'permissions.oauth': {
url : 'dialog/oauth',
size : { width: (FB.UA.mobile() ? null : 627),
height: (FB.UA.mobile() ? null : 326) },
transform : function(call) {
if (!FB._apiKey) {
FB.log('FB.login() called before FB.init().');
return;
}
// if an access token is already available and no additional
// params are being requested (via a scope attribute within the params)
// then the callback should be pinged directly without the round trip.
if (FB._authResponse && !call.params.scope) {
FB.log('FB.login() called when user is already connected.');
call.cb && call.cb({ status: FB._userStatus,
authResponse: FB._authResponse });
return;
}
var
cb = call.cb,
id = call.id;
delete call.cb;
FB.copy(
call.params, {
client_id : FB._apiKey,
redirect_uri : FB.URI.resolve(
FB.Auth.xdHandler(
cb,
id,
'opener',
FB._authResponse,
'permissions.oauth')),
origin : FB.Auth._getContextType(),
response_type: 'token,signed_request',
domain: location.hostname
});
return call;
}
},
'auth.logout': {
url : 'logout.php',
transform : function(call) {
if (!FB._apiKey) {
FB.log('FB.logout() called before calling FB.init().');
} else if (!FB._authResponse) {
FB.log('FB.logout() called without an access token.');
} else {
call.params.next = FB.Auth.xdHandler(call.cb,
call.id,
'parent',
FB._authResponse);
return call;
}
}
},
'login.status': {
url : 'dialog/oauth',
transform : function(call) {
var
cb = call.cb,
id = call.id;
delete call.cb;
FB.copy(call.params, {
client_id : FB._apiKey,
redirect_uri : FB.Auth.xdHandler(cb,
id,
'parent',
FB._authResponse),
origin : FB.Auth._getContextType(),
response_type : 'token,signed_request,code',
domain: location.hostname
});
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.
*
*
*
* Contains the public method ``FB.Insights.setDoneLoading`` for tracking
* application load times
*
* @provides fb.canvas.insights
* @requires fb.canvas
*/
/**
* @class FB.CanvasInsights
* @static
* @access public
*/
FB.provide('CanvasInsights', {
/**
* Deprecated - use FB.Canvas.setDoneLoading
*/
setDoneLoading : function(callback) {
FB.Canvas.setDoneLoading(callback);
}
});
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*
* @provides fb.cookie
* @requires fb.prelude
* fb.qs
* fb.event
*/
/**
* Cookie Support.
*
* @class FB.Cookie
* @static
* @access private
*/
FB.provide('Cookie', {
/**
* Holds the base_domain property to match the Cookie domain.
*
* @access private
* @type String
*/
_domain: null,
/**
* Indicate if Cookie support should be enabled.
*
* @access private
* @type Boolean
*/
_enabled: false,
/**
* Enable or disable Cookie support.
*
* @access private
* @param val {Boolean} true to enable, false to disable
*/
setEnabled: function(val) {
FB.Cookie._enabled = !!val;
if (typeof val == 'string') {
FB.Cookie._domain = val;
}
},
/**
* Return the current status of the cookie system.
*
* @access private
* @returns {Boolean} true if Cookie support is enabled
*/
getEnabled: function() {
return FB.Cookie._enabled;
},
/**
* Try loading metadata from the unsecure fbm_ cookie
*
* @access private
* @return {Object} the meta data for for the connect implementation
*/
loadMeta: function() {
var
// note, we have the opening quote for the value in the regex, but do
// not have a closing quote. this is because the \b already handles it.
cookie = document.cookie.match('\\bfbm_' + FB._apiKey + '=([^;]*)\\b'),
meta;
if (cookie) {
// url encoded session stored as "sub-cookies"
meta = FB.QS.decode(cookie[1]);
if (!FB.Cookie._domain) {
// capture base_domain for use when we need to clear
FB.Cookie._domain = meta.base_domain;
}
}
return meta;
},
/**
* Try loading the signedRequest from the cookie if one is found.
*
* @return {String} the cached signed request, or null if one can't be found.
*/
loadSignedRequest: function() {
var cookie =
document.cookie.match('\\bfbsr_' + FB._apiKey + '=([^;]*)\\b');
if (!cookie) {
return null;
}
return cookie[1];
},
/**
* Set the signed request cookie to something nonempty
* and without expiration time, or clear it if the cookie is
* missing or empty.
*
* @access private
* @param {String} signed_request_cookie the code/user_id cookie
* in signed request format.
* @param {Integer} The time at which the cookie should expire.
* @param {String} The domain for which this cookie should be set.
*/
setSignedRequestCookie: function(signed_request_cookie, expiration_time,
base_domain) {
if (!signed_request_cookie) {
throw new Error('Value passed to FB.Cookie.setSignedRequestCookie ' +
'was empty.');
}
if (!FB.Cookie.getEnabled()) {
return;
}
if (base_domain) {
// store this so that we can use it when deleting the cookie
var meta = FB.QS.encode({
base_domain: base_domain
});
FB.Cookie.setRaw('fbm_', meta, expiration_time, base_domain);
}
FB.Cookie._domain = base_domain;
FB.Cookie.setRaw('fbsr_', signed_request_cookie, expiration_time,
base_domain);
},
/**
* Clears the signed request cookie normally set by
* setSignedRequestCookie above.
*/
clearSignedRequestCookie: function() {
if (!FB.Cookie.getEnabled()) {
return;
}
FB.Cookie.setRaw('fbsr_', '', 0, FB.Cookie._domain);
},
/**
* Helper function to set cookie value.
*
* @access private
* @param prefix {String} short string namespacing the cookie
* @param val {String} the string value (should already be encoded)
* @param ts {Number} a unix timestamp denoting expiration
* @param domain {String} optional domain for cookie
*/
setRaw: function(prefix, val, ts, domain) {
// Start by clearing potentially overlapping cookies
if (domain) {
// No domain set (will become example.com)
document.cookie =
prefix + FB._apiKey + '=; expires=Wed, 04 Feb 2004 08:00:00 GMT;';
// This domain, (will become .example.com)
document.cookie =
prefix + FB._apiKey + '=; expires=Wed, 04 Feb 2004 08:00:00 GMT;' +
'domain=' + location.hostname + ';';
}
var expires = new Date(ts).toGMTString();
document.cookie =
prefix + FB._apiKey + '=' + val +
(val && ts === 0 ? '' : '; expires=' + expires) +
'; path=/' +
(domain ? '; domain=' + domain : '');
}
});
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @provides fb.frictionless
* @requires fb.prelude
* fb.api
* fb.array
* fb.auth
* fb.event
* fb.string
*/
/**
* Frictionless request recipient list management.
*
* @class FB.Frictionless
* @static
* @private
*/
FB.provide('Frictionless', {
// mapping of user id to boolean indicating whether that recipient can receive
// frictionless requests
_allowedRecipients: {},
_useFrictionless: false,
/**
* Requests the frictionless request recipient list via a graph api call.
*/
_updateRecipients: function() {
FB.Frictionless._allowedRecipients = {};
FB.api('/me/apprequestformerrecipients', function(response) {
if (!response || response.error) {
return;
}
FB.Array.forEach(response.data, function(recipient) {
FB.Frictionless._allowedRecipients[recipient.recipient_id] = true;
}, false);
});
},
/**
* Subscribes to login event and updates recipients when it fire.
*/
init: function() {
FB.Frictionless._useFrictionless = true;
FB.getLoginStatus(function(response) {
if (response.status == 'connected') {
FB.Frictionless._updateRecipients();
}
});
FB.Event.subscribe('auth.login', function(login) {
if (login.authResponse) {
FB.Frictionless._updateRecipients();
}
});
},
/**
* Returns a callback function wrapper that updates the recipient list
* before calling the wrapped user callback
*
* @param cb {Function} the user callback function to wrap
* @return {Function} the wrapped callback function
*/
_processRequestResponse: function(cb, hidden) {
return function(params) {
var updated = params && params.updated_frictionless;
if (FB.Frictionless._useFrictionless && updated) {
// only update the recipient list if the request dialog was shown and
// this is a frictionless app
FB.Frictionless._updateRecipients();
}
if (params) {
if (!hidden && params.frictionless) {
FB.Dialog._hideLoader();
FB.Dialog._restoreBodyPosition();
FB.Dialog._hideIPadOverlay();
}
delete params.frictionless;
delete params.updated_frictionless;
}
// call user callback
cb && cb(params);
};
},
/**
* Checks if a set of user ids are all in the frictionless request recipient
* list. Handles number, string, and array inputs.
*
* @param user_ids {String|Number|Array} the user ids to test
* @return {Boolean} whether all user ids allow frictionless requests
*/
isAllowed: function(user_ids) {
if (!user_ids) {
return false;
}
if (typeof user_ids === 'number') {
return FB.Frictionless._allowedRecipients[user_ids];
}
if (typeof user_ids === 'string') {
user_ids = user_ids.split(',');
}
user_ids = FB.Array.map(user_ids, FB.String.trim);
var allowed = true;
var has_user_ids = false;
FB.Array.forEach(user_ids, function(user_id) {
allowed = allowed && FB.Frictionless._allowedRecipients[user_id];
has_user_ids = true;
}, false);
return allowed && has_user_ids;
}
});
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*
* JavaScript library providing Facebook Connect integration.
*
* @provides fb.canvas.prefetcher
* @requires fb.array
* fb.init
* fb.json
* fb.prelude
*/
/**
* This class samples applications' resources and uploads them to Facebook so
* that they may be flushed early if they are used frequently enough.
* @class FB.Canvas.Prefetcher
* @static
* @access public
*/
FB.provide('Canvas.Prefetcher', {
_sampleRate : 0,
_appIdsBlacklist : [],
_links : [],
COLLECT_AUTOMATIC : 0,
COLLECT_MANUAL : 1,
_collectionMode : 0, // COLLECT_AUTOMATIC
/**
* Add a url for a resource that was used on this page load, but which did not
* appear in the DOM (and thus Facebook could not detect it automatically).
*/
addStaticResource: function(url) {
if (!FB._inCanvas || !FB._apiKey) {
return;
}
FB.Canvas.Prefetcher._links.push(url);
},
/**
* Set collection mode:
* FB.Canvas.Prefetcher.COLLECT_AUTOMATIC (default) -
* FB automatically scrapes your application's DOM to
* determine which resources to report. It will pick the top several.
* You can also call addResource() to inform FB of resources it missed.
* FB.Canvas.Prefetcher.COLLECT_MANUAL -
* FB does not automatically scrape your application. Use this
* mode to completely control which resources are reported. Also, you
* can use it to turn off early flush, or turn it off for certain page
* loads that you don't want to affect the statistics.
*
* Returns: true on success.
*/
setCollectionMode: function(mode) {
if (!FB._inCanvas || !FB._apiKey) {
return false;
}
if (mode != FB.Canvas.Prefetcher.COLLECT_AUTOMATIC &&
mode != FB.Canvas.Prefetcher.COLLECT_MANUAL) {
return false;
}
FB.Canvas.Prefetcher._collectionMode = mode;
},
_maybeSample : function() {
if (!FB._inCanvas || !FB._apiKey || !FB.Canvas.Prefetcher._sampleRate) {
return;
}
var rand = Math.random();
if (rand > 1 / FB.Canvas.Prefetcher._sampleRate) {
return;
}
if (FB.Canvas.Prefetcher._appIdsBlacklist == '*') {
return;
}
if (FB.Array.indexOf(
FB.Canvas.Prefetcher._appIdsBlacklist,
parseInt(FB._apiKey, 10)) != -1) {
return;
}
// We are definitely taking a sample. Wait 30 seconds to take it.
window.setTimeout(FB.Canvas.Prefetcher._sample, 30000);
},
_sample : function() {
// For now, get some random tags. Will do a more complete job later.
var resourceFieldsByTag = {
object: 'data',
link: 'href',
script: 'src'
};
// Application wants control over what resources are flushed
if (FB.Canvas.Prefetcher._collectionMode ==
FB.Canvas.Prefetcher.COLLECT_AUTOMATIC) {
FB.Array.forEach(resourceFieldsByTag, function(propertyName, tagName) {
FB.Array.forEach(
window.document.getElementsByTagName(tagName), function(tag) {
if (tag[propertyName]) {
FB.Canvas.Prefetcher._links.push(tag[propertyName]);
}
});
});
}
// Hit API with a JSON array of links to resources
var payload = FB.JSON.stringify(FB.Canvas.Prefetcher._links);
FB.api(FB._apiKey + '/staticresources', 'post',
{ urls: payload, is_https: FB._https });
FB.Canvas.Prefetcher._links = [];
}
});
/**
* Deprecated - use FB.Canvas.Prefetcher
* @class FB.Canvas.EarlyFlush
* @static
* @access public
*/
FB.provide('Canvas.EarlyFlush', {
addResource: function(url) {
return FB.Canvas.Prefetcher.addStaticResource(url);
},
setCollectionMode: function(mode) {
return FB.Canvas.Prefetcher.setCollectionMode(mode);
}
});
/**
* Copyright Facebook Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*
* JavaScript library providing Facebook Connect integration.
*
* @provides fb.init
* @requires fb.prelude
* fb.auth
* fb.api
* fb.canvas
* fb.canvas.prefetcher
* fb.cookie
* fb.frictionless
* fb.ui
* fb.ua
* fb.xd
*/
/**
* This is the top level for all the public APIs.
*
* @class FB
* @static
* @access public
*/
FB.provide('', {
// set by CONNECT_FB_INIT_CONFIG
initSitevars : {},
/**
* Initialize the library.
*
* Typical initialization enabling all optional features:
*
* <div id="fb-root"></div>
* <script src="http://connect.facebook.net/en_US/all.js"></script>
* <script>
* FB.init({
* appId : 'YOUR APP ID',
* status : true, // check login status
* cookie : true, // cookies allow server access to signed_request
* xfbml : true // parse XFBML
* });
* </script>
*
* The best place to put this code is right before the closing
* `</body>` tag.
*
* ### Asynchronous Loading
*
* The library makes non-blocking loading of the script easy to use by
* providing the `fbAsyncInit` hook. If this global function is defined, it
* will be executed when the library is loaded:
*
* <div id="fb-root"></div>
* <script>
* window.fbAsyncInit = function() {
* FB.init({
* appId : 'YOUR APP ID',
* status : true, // check login status
* cookie : true, // cookies allow server access to signed_request
* xfbml : true // parse XFBML
* });
* };
*
* (function() {
* var e = document.createElement('script');
* e.src = document.location.protocol +
* '//connect.facebook.net/en_US/all.js';
* e.async = true;
* document.getElementById('fb-root').appendChild(e);
* }());
* </script>
*
* The best place to put the asynchronous version of the code is right after
* the opening `<body>` 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`
* status | Boolean | `false` to disable status ping. | *Optional* | `true`
* xfbml | Boolean | `true` to parse [[wiki:XFBML]] tags. | *Optional* | `false`
* useCachedDialogs | Boolean | `false` to disable cached dialogs | *Optional* | `true`
* frictionlessRequests | Boolean | `true` to enable frictionless requests | *Optional* | `false`
* authResponse | Object | Use specified access token record | *Optional* | `null`
* hideFlashCallback | function | (Canvas Only) callback for each flash element when popups overlay the page | *Optional* | `null`
*/
init: function(options) {
// only need to list values here that do not already have a falsy default.
// this is why cookie/authResponse are not listed here.
options = FB.copy(options || {}, {
logging: true,
status: true
});
FB._userID = 0; // assume unknown or disconnected unless proved otherwise
FB._apiKey = options.appId || options.apiKey;
// CORDOVA PATCH
// if nativeInterface is specified then fire off the native initialization as well.
FB._nativeInterface = options.nativeInterface;
if (FB._nativeInterface) {
FB._nativeInterface.init(FB._apiKey, function(e) {alert('Cordova Facebook Connect plugin fail on init!');});
}
// 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.UA.mobile() && FB.TemplateUI &&
FB.TemplateData && FB.TemplateData._enabled &&
options.useCachedDialogs !== false) {
FB.TemplateUI.init();
FB.Event.subscribe('auth.statusChange', FB.TemplateData.update);
}
if (options.reportTemplates) {
FB.reportTemplates = true;
}
if (options.frictionlessRequests) {
FB.Frictionless.init();
}
if (FB._apiKey) {
// enable cookie support if told to do so
FB.Cookie.setEnabled(options.cookie);
if (options.authResponse) {
FB.Auth.setAuthResponse(options.authResponse,
'connected');
} else {
// we don't have an access token yet, but we might have a user
// ID based on a signed request in the cookie.
var signedRequest = FB.Cookie.loadSignedRequest();
var parsedSignedRequest = FB.Auth.parseSignedRequest(signedRequest);
FB._userID =
(parsedSignedRequest && parsedSignedRequest.user_id) || 0;
FB.Cookie.loadMeta();
}
// load a fresh authRequest (or access token) if requested
if (options.status) {
FB.getLoginStatus();
}
}
if (FB._inCanvas) {
FB.Canvas._setHideFlashCallback(options.hideFlashCallback);
FB.Canvas.init();
}
FB.Event.subscribe('xfbml.parse', function() {
FB.XFBML.IframeWidget.batchWidgetPipeRequests();
});
// 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) {
if (FB.initSitevars.parseXFBMLBeforeDomReady) {
// poll to render new elements as fast as possible,
// without waiting for things like external js to load
FB.XFBML.parse();
var myI = window.setInterval(
function() {
FB.XFBML.parse();
},
100);
FB.Dom.ready(
function() {
window.clearInterval(myI);
FB.XFBML.parse();
});
} else {
// traditional xfbml parse after dom is loaded
FB.Dom.ready(FB.XFBML.parse);
}
}
}, 0);
}
if (FB.Canvas && FB.Canvas.Prefetcher) {
FB.Canvas.Prefetcher._maybeSample();
}
}
});
/**
* 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
* fb.ua
*/
/**
* Handler for UI dialogs that can be created as iframe dialog
* on mobile browser.
*/
FB.provide('UIServer.MobileIframableMethod', {
transform: function(call) {
// For mobile display on mobile browsers that support
// postMessage, we use iframe dialog if the app has
// access token
if (call.params.display === 'touch' &&
call.params.access_token &&
window.postMessage
) {
// Prepare for iframe by adding an channel parameter
// for resizing and add in_iframe parameter
call.params.channel = FB.UIServer._xdChannelHandler(
call.id,
'parent'
);
// No iframe for modal dialogs in the native app
if (!FB.UA.nativeApp()) {
call.params.in_iframe = 1;
}
return call;
} else {
return FB.UIServer.genericTransform(call);
}
},
getXdRelation: function(params) {
var display = params.display;
if (display === 'touch' && window.postMessage && params.in_iframe) {
// mobile iframe directly use postMessage to communicate
// with parent window for resizing, so the xd relation should be
// parent instead of parent.parent.
return 'parent';
}
return FB.UIServer.getXdRelation(params);
}
});
/**
* Simple UI methods. Consider putting complex UI methods in their own modules.
*/
FB.provide('UIServer.Methods', {
'stream.share': {
size : { width: 650, height: 340 },
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,
// if we left transform blank, it would default to UI Server's transform
transform : function(call) { return call; }
},
// Just logs a user into Facebook and does NOT TOS the application
'auth.logintofacebook': {
size : { width: 530, height: 287 },
url : 'login.php',
transform : function(call) {
// login.php will redirect you to uiserver if you have an api_key
// without this param
call.params.skip_api_login = 1;
// login.php won't let you put the XDhandler as your next url unless you
// are already logged in. After many attempts to do this with login.php,
// it is easier to log you in, then send you back to login.php with the
// next handler as the parameter. Feel free to do this in login.php
// instead, if you can figure it out.
var relation = FB.UIServer.getXdRelation(call.params);
var next = FB.UIServer._xdResult(
call.cb,
call.id,
relation,
true // isDefault
);
call.params.next = FB.getDomain(FB._https ? 'https_www' : 'www') +
"login.php?" + FB.QS.encode({
api_key: FB._apiKey,
next: next,
skip_api_login: 1
});
return call;
}
},
// Some extra stuff happens in FB.Frictionless.init() for this next one
'apprequests': {
transform: function(call) {
call = FB.UIServer.MobileIframableMethod.transform(call);
call.params.frictionless = FB.Frictionless &&
FB.Frictionless._useFrictionless;
if (call.params.frictionless) {
if (FB.Frictionless.isAllowed(call.params.to)) {
// Always use iframe (instead of popup or new webview)
// for frictionless request that's already
// enabled for the current user and app because this action
// will be UI less.
call.params.in_iframe = true;
// hide load screen if this is a frictionless request
call.hideLoader = true;
}
// wrap user specified callback
call.cb = FB.Frictionless._processRequestResponse(
call.cb,
call.hideLoader
);
}
return call;
},
getXdRelation: function(params) {
return FB.UIServer.MobileIframableMethod.getXdRelation(params);
}
},
'feed': FB.UIServer.MobileIframableMethod
});
/**
* 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.ui.methods
* 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);
}
});
// the "fake" UIServer method was called auth.login
FB.UIServer.Methods['auth.login'] = FB.UIServer.Methods['permissions.request'];
/**
* 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.xfbml
* @layer xfbml
* @requires fb.prelude
* fb.array
* fb.dom
* fb.ua
*/
/**
* Methods for the rendering of [[wiki:XFBML]] tags.
*
* To render the tags, simply put the tags anywhere in your page, and then
* call:
*
* FB.XFBML.parse();
*
* @class FB.XFBML
* @static
*/
FB.provide('XFBML', {
/**
* The time allowed for all tags to finish rendering.
*
* @type Number
*/
_renderTimeout: 30000,
/**
* Finds elements that will become plugins.
* Looks for <fb:like> and <div class="fb-like">
*
* @param dom {DOMElement} the root DOM node
* @param xmlns {String} the XML namespace
* @param localName {String} the unqualified tag name
* @return {Array}
*/
getElements: function(dom, xmlns, localName) {
var arr = FB.Array,
xfbmlDoms = FB.XFBML._getDomElements(dom, xmlns, localName),
html5Doms = FB.Dom.getByClass(xmlns + '-' + localName, dom, 'div');
xfbmlDoms = arr.toArray(xfbmlDoms);
html5Doms = arr.toArray(html5Doms);
// filter out html5 candidates that are not empty
// to prevent cases like <div class="fb-like"><fb:like></fb:like></div>
html5Doms = arr.filter(html5Doms, function(el) {
// let's throw it out unless
// the child is just one (1) empty text node (nodeType 3)
return !el.hasChildNodes() ||
(el.childNodes.length === 1 && el.childNodes[0].nodeType === 3);
});
return arr.merge(xfbmlDoms, html5Doms);
},
/**
* Parse and render XFBML markup in the document. XFBML enables you to
* incorporate [FBML](../fbml) into your websites and IFrame applications.
*
* You can parse the following XFBML tags with this method:
*
* * [fb:activity](../plugins/activity)
* * [fb:add-profile-tab](../fbml/add-profile-tab)
* * [fb:add-to-timeline](../plugins/add-to-timeline)
* * [fb:bookmark](../fbml/bookmark)
* * [fb:comments](../plugins/comments)
* * [fb:facepile](../plugins/facepile)
* * [fb:like](../plugins/like)
* * [fb:like-box](../plugins/like-box)
* * [fb:live-stream](../plugins/live-stream)
* * [fb:login-button](../plugins/login)
* * [fb:pronoun](../fbml/pronoun)
* * [fb:recommendations](../plugins/recommendations)
* * [fb:serverFbml](../fbml/serverFbml)
* * [fb:user-status](../fbml/user-status)
*
* Examples
* --------
*
* By default, this is all you need to make XFBML work:
*
* FB.XFBML.parse();
*
* Alternately, you may want to only evaluate a portion of
* the document. In that case, you can pass in the elment.
*
* FB.XFBML.parse(document.getElementById('foo'));
*
* @access public
* @param dom {DOMElement} (optional) root DOM node, defaults to body
* @param cb {Function} (optional) invoked when elements are rendered
*/
parse: function(dom, cb) {
dom = dom || document.body;
// We register this function on each tag's "render" event. This allows us
// to invoke the callback when we're done rendering all the found elements.
//
// We start with count=1 rather than 0, and finally call onTagDone() after
// we've kicked off all the tag processing. This ensures that we do not hit
// count=0 before we're actually done queuing up all the tags.
var
count = 1,
onTagDone = function() {
count--;
if (count === 0) {
// Invoke the user specified callback for this specific parse() run.
cb && cb();
// Also fire a global event. A global event is fired for each
// invocation to FB.XFBML.parse().
FB.Event.fire('xfbml.render');
}
};
var cachedDomElements = {};
if (FB.XFBML._widgetPipeIsEnabled()) {
// first count the number of XFBML tags in the page that would benefit
// from a transition to use Widget Pipe. We do this, because we only
// want to engage widget pipe if the overhead is counterbalanced by the
// server-side generation time. And to make sure we don't scan for,
// say, all fb:like tags twice, we cache them so they can be referred
// during the loop that formally processes all of the tags.
FB.Array.forEach(FB.XFBML._tagInfos, function(tagInfo) {
if (tagInfo.supportsWidgetPipe) {
var xmlns = tagInfo.xmlns ? tagInfo.xmlns : 'fb';
var xfbmlDoms = FB.XFBML.getElements(dom, xmlns, tagInfo.localName);
cachedDomElements[tagInfo.localName] = xfbmlDoms;
FB.XFBML._widgetPipeEnabledTagCount += xfbmlDoms.length;
}
});
}
// First, find all tags that are present
FB.Array.forEach(FB.XFBML._tagInfos, function(tagInfo) {
// default the xmlns if needed
if (!tagInfo.xmlns) {
tagInfo.xmlns = 'fb';
}
var xfbmlDoms;
if (cachedDomElements[tagInfo.localName] !== undefined) {
xfbmlDoms = cachedDomElements[tagInfo.localName];
} else {
xfbmlDoms = FB.XFBML.getElements(
dom,
tagInfo.xmlns,
tagInfo.localName
);
}
for (var i=0; i < xfbmlDoms.length; i++) {
count++;
FB.XFBML._processElement(xfbmlDoms[i], tagInfo, onTagDone);
}
});
// Inform all tags that they've at least been processed, even if their
// content hasn't quite been fetched yet.
FB.Event.fire('xfbml.parse');
// Setup a timer to ensure all tags render within a given timeout
window.setTimeout(function() {
if (count > 0) {
FB.log(
count + ' XFBML tags failed to render in ' +
FB.XFBML._renderTimeout + 'ms.'
);
}
}, FB.XFBML._renderTimeout);
// Call once to handle count=1 as described above.
onTagDone();
},
/**
* Register a custom XFBML tag. If you create an custom XFBML tag, you can
* use this method to register it so the it can be treated like
* any build-in XFBML tags.
*
* Example
* -------
*
* Register fb:name tag that is implemented by class FB.XFBML.Name
* tagInfo = {xmlns: 'fb',
* localName: 'name',
* className: 'FB.XFBML.Name'},
* FB.XFBML.registerTag(tagInfo);
*
* @access private
* @param {Object} tagInfo
* an object containiner the following keys:
* - xmlns
* - localName
* - className
*/
registerTag: function(tagInfo) {
FB.XFBML._tagInfos.push(tagInfo);
},
/**
* Decides on behalf of the entire document whether
* using WidgetPipe is even worth it.
*
* @return {Boolean} true if and only if the number of
* widget-pipe-compatible tags exceeds a certain
* threshold.
*/
shouldUseWidgetPipe: function() {
if (!FB.XFBML._widgetPipeIsEnabled()) {
return false;
}
var aboveThreshold =
FB.XFBML._widgetPipeEnabledTagCount > 1;
return aboveThreshold;
},
/**
* Return a boolean value for a DOM attribute
*
* @param el {HTMLElement} DOM element
* @param attr {String} Attribute name
*/
getBoolAttr: function(el, attr) {
attr = FB.XFBML.getAttr(el, attr);
return (attr && FB.Array.indexOf(
['true', '1', 'yes', 'on'],
attr.toLowerCase()) > -1);
},
/**
* Return a value for a DOM attribute if exists
* Checks the attrib name verbatim as well as
* prepended with data-*
*
* @param el {HTMLElement} DOM element
* @param attr {String} Attribute name
*/
getAttr: function(el, attr) {
return (
el.getAttribute(attr) ||
el.getAttribute(attr.replace(/_/g, '-')) ||
el.getAttribute(attr.replace(/-/g, '_')) ||
el.getAttribute(attr.replace(/-/g, '')) ||
el.getAttribute(attr.replace(/_/g, '')) ||
el.getAttribute('data-' + attr) ||
el.getAttribute('data-' + attr.replace(/_/g, '-')) ||
el.getAttribute('data-' + attr.replace(/-/g, '_')) ||
el.getAttribute('data-' + attr.replace(/-/g, '')) ||
el.getAttribute('data-' + attr.replace(/_/g, '')) ||
null
);
},
//////////////// Private methods ////////////////////////////////////////////
/**
* Process an XFBML element.
*
* @access private
* @param dom {DOMElement} the dom node
* @param tagInfo {Object} the tag information
* @param cb {Function} the function to bind to the "render" event for the tag
*/
_processElement: function(dom, tagInfo, cb) {
// Check if element for the dom already exists
var element = dom._element;
if (element) {
element.subscribe('render', cb);
element.process();
} else {
var processor = function() {
var fn = eval(tagInfo.className);
// TODO(naitik) cleanup after f8
//
// currently, tag initialization is done via a constructor function,
// there by preventing a tag implementation to vary between two types
// of objects. post f8, this should be changed to a factory function
// which would allow the login button to instantiate the Button based
// tag or Iframe based tag depending on the attribute value.
var isLogin = false;
var showFaces = true;
var showLoginFace = false;
var renderInIframe = false;
var addToTimeline = (tagInfo.className === 'FB.XFBML.AddToTimeline');
if ((tagInfo.className === 'FB.XFBML.LoginButton') || addToTimeline) {
renderInIframe = FB.XFBML.getBoolAttr(dom, 'render-in-iframe');
mode = FB.XFBML.getAttr(dom, 'mode');
showFaces = (addToTimeline && mode != 'button') ||
FB.XFBML.getBoolAttr(dom, 'show-faces');
showLoginFace = FB.XFBML.getBoolAttr(dom, 'show-login-face');
isLogin = addToTimeline ||
renderInIframe ||
showFaces ||
showLoginFace ||
FB.XFBML.getBoolAttr(dom, 'oneclick');
if (isLogin && !addToTimeline) {
// override to be facepile-ish for an app id
fn = FB.XFBML.Login;
}
}
element = dom._element = new fn(dom);
if (isLogin) {
showFaces = !!showFaces;
showLoginFace = !!showLoginFace;
var extraParams = {show_faces: showFaces,
show_login_face: showLoginFace,
add_to_profile: addToTimeline,
mode: mode};
// For now we support both the perms and scope attribute
var scope = FB.XFBML.getAttr(dom, 'scope') ||
FB.XFBML.getAttr(dom, 'perms');
if (scope) {
extraParams.scope = scope;
}
element.setExtraParams(extraParams);
}
element.subscribe('render', cb);
element.process();
};
if (FB.CLASSES[tagInfo.className.substr(3)]) {
processor();
} else {
FB.log('Tag ' + tagInfo.className + ' was not found.');
}
}
},
/**
* Get all the DOM elements present under a given node with a given tag name.
*
* @access private
* @param dom {DOMElement} the root DOM node
* @param xmlns {String} the XML namespace
* @param localName {String} the unqualified tag name
* @return {DOMElementCollection}
*/
_getDomElements: function(dom, xmlns, localName) {
// Different browsers behave slightly differently in handling tags
// with custom namespace.
var fullName = xmlns + ':' + localName;
if (FB.UA.firefox()) {
// Use document.body.namespaceURI as first parameter per
// suggestion by Firefox developers.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=531662
return dom.getElementsByTagNameNS(document.body.namespaceURI, fullName);
} else if (FB.UA.ie() < 9) {
// accessing document.namespaces when the library is being loaded
// asynchronously can cause an error if the document is not yet ready
try {
var docNamespaces = document.namespaces;
if (docNamespaces && docNamespaces[xmlns]) {
var nodes = dom.getElementsByTagName(localName);
// if it's a modern browser, and we haven't found anything yet, we
// can try the fallback below, otherwise return whatever we found.
if (!document.addEventListener || nodes.length > 0) {
return nodes;
}
}
} catch (e) {
// introspection doesn't yield any identifiable information to scope
}
// It seems that developer tends to forget to declare the fb namespace
// in the HTML tag (xmlns:fb="http://ogp.me/ns/fb#") IE
// has a stricter implementation for custom tags. If namespace is
// missing, custom DOM dom does not appears to be fully functional. For
// example, setting innerHTML on it will fail.
//
// If a namespace is not declared, we can still find the element using
// GetElementssByTagName with namespace appended.
return dom.getElementsByTagName(fullName);
} else {
return dom.getElementsByTagName(fullName);
}
},
/**
* Register the default set of base tags. Each entry must have a localName
* and a className property, and can optionally have a xmlns property which
* if missing defaults to 'fb'.
*
* NOTE: Keep the list alpha sorted.
*/
_tagInfos: [
{ localName: 'activity', className: 'FB.XFBML.Activity' },
{ localName: 'add-profile-tab', className: 'FB.XFBML.AddProfileTab' },
{ localName: 'add-to-timeline', className: 'FB.XFBML.AddToTimeline' },
{ localName: 'bookmark', className: 'FB.XFBML.Bookmark' },
{ localName: 'comments', className: 'FB.XFBML.Comments' },
{ localName: 'comments-count', className: 'FB.XFBML.CommentsCount' },
{ localName: 'connect-bar', className: 'FB.XFBML.ConnectBar' },
{ localName: 'fan', className: 'FB.XFBML.Fan' },
{ localName: 'like', className: 'FB.XFBML.Like',
supportsWidgetPipe: true },
{ localName: 'like-box', className: 'FB.XFBML.LikeBox' },
{ localName: 'live-stream', className: 'FB.XFBML.LiveStream' },
{ localName: 'login', className: 'FB.XFBML.Login' },
{ localName: 'login-button', className: 'FB.XFBML.LoginButton' },
{ localName: 'facepile', className: 'FB.XFBML.Facepile' },
{ localName: 'friendpile', className: 'FB.XFBML.Friendpile' },
{ localName: 'name', className: 'FB.XFBML.Name' },
{ localName: 'profile-pic', className: 'FB.XFBML.ProfilePic' },
{ localName: 'question', className: 'FB.XFBML.Question' },
{ localName: 'recommendations', className: 'FB.XFBML.Recommendations' },
{ localName: 'recommendations-bar',
className: 'FB.XFBML.RecommendationsBar' },
{ localName: 'registration', className: 'FB.XFBML.Registration' },
{ localName: 'send', className: 'FB.XFBML.Send' },
{ localName: 'serverfbml', className: 'FB.XFBML.ServerFbml' },
{ localName: 'share-button', className: 'FB.XFBML.ShareButton' },
{ localName: 'social-context', className: 'FB.XFBML.SocialContext' },
{ localName: 'subscribe', className: 'FB.XFBML.Subscribe' }
],
// the number of widget-pipe-compatible tags we found in the DOM
_widgetPipeEnabledTagCount: 0,
/**
* Returns true if and only if we're willing to try out WidgetPipe
* in hopes of increasing plugin parallelization.
*/
_widgetPipeIsEnabled: function() {
return FB.widgetPipeEnabledApps &&
FB.widgetPipeEnabledApps[FB._apiKey] !== undefined;
}
});
/*
* For IE, we will try to detect if document.namespaces contains 'fb' already
* and add it if it does not exist.
*/
// wrap in a try/catch because it can throw an error if the library is loaded
// asynchronously and the document is not ready yet
(function() {
try {
if (document.namespaces && !document.namespaces.item.fb) {
document.namespaces.add('fb');
}
} catch(e) {
// introspection doesn't yield any identifiable information to scope
}
}());
/**
* 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.xfbml
* @requires fb.prelude
* fb.xfbml
*/
/**
* Methods for the rendering of [[wiki:XFBML]] tags.
*
* To render the tags, simply put the tags anywhere in your page, and then
* call:
*
* FB.XFBML.parse();
*
* @class FB.XFBML
* @static
*/
FB.provide('XFBML', {
/**
* NOTE: This method is deprecated. You should instead set the innerHTML
* yourself and call FB.XFBML.parse() on the DOMElement after.
*
* Dynamically set XFBML markup on a given DOM element. Use this
* method if you want to set XFBML after the page has already loaded
* (for example, in response to an Ajax request or API call).
*
* Example:
* --------
* Set the innerHTML of a dom element with id "container"
* to some markup (fb:name + regular HTML) and render it
*
* FB.XFBML.set(FB.$('container'),
* '<fb:name uid="4"></fb:name><div>Hello</div>');
*
* @access private
* @param {DOMElement} dom DOM element
* @param {String} markup XFBML markup. It may contain reguarl
* HTML markup as well.
*/
set: function(dom, markup, cb) {
FB.log('FB.XFBML.set() has been deprecated.');
dom.innerHTML = markup;
FB.XFBML.parse(dom, 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.waitable
* @layer data
* @requires fb.prelude fb.type fb.string fb.array fb.event fb.obj
*/
/**
* A container for asynchronous data that may not be available immediately.
* This is base type for results returned from FB.Data.query()
* method.
*
* @class FB.Waitable
*/
FB.subclass('Waitable', 'Obj',
/**
* Construct a Waitable object.
*
* @access private
* @constructor
*/
function() {},
{
/**
* Set value property of the data object. This will
* cause "value" event to be fire on the object. Any callback functions
* that are waiting for the data through wait() methods will be invoked
* if the value was previously not set.
*
* @private
* @param {Object} value new value for the Waitable
*/
set: function(value) {
this.setProperty('value', value);
},
/**
* Fire the error event.
*
* @access private
* @param ex {Exception} the exception object
*/
error: function(ex) {
this.fire("error", ex);
},
/**
* Register a callback for an asynchronous value, which will be invoked when
* the value is ready.
*
* Example
* -------
*
* In this
* val v = get_a_waitable();
* v.wait(function (value) {
* // handle the value now
* },
* function(error) {
* // handle the errro
* });
* // later, whoever generated the waitable will call .set() and
* // invoke the callback
*
* @param {Function} callback A callback function that will be invoked
* when this.value is set. The value property will be passed to the
* callback function as a parameter
* @param {Function} errorHandler [optional] A callback function that
* will be invoked if there is an error in getting the value. The errorHandler
* takes an optional Error object.
*/
wait: function(callback, errorHandler) {
// register error handler first incase the monitor call causes an exception
if (errorHandler) {
this.subscribe('error', errorHandler);
}
this.monitor('value', this.bind(function() {
if (this.value !== undefined) {
callback(this.value);
return true;
}
}));
}
});
/**
* 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.data.query
* @layer data
* @requires fb.waitable
*/
/**
* Object that represents the results of an asynchronous FQL query, typically
* constructed by a call [FB.Data.query](FB.Data.query)().
*
* These objects can be used in one of two ways:
*
* * Call [wait](FB.Waitable.wait)() to handle the value when it's ready:
*
* var query = FB.Data.query(
* 'select name from page where username = 'barackobama');
* query.wait(function(result) {
* document.getElementById('page').innerHTML = result[0].name
* });
*
* * Pass it as an argument to a function that takes a Waitable. For example,
* in this case you can construct the second query without waiting for the
* results from the first, and it will combine them into one request:
*
* var query = FB.Data.query(
* 'select username from page where page_id = 6815841748');
* var dependentQuery = FB.Data.query(
* 'select name from page where username in ' +
* '(select username from {0})', query);
*
* // now wait for the results from the dependent query
* dependentQuery.wait(function(data) {
* document.getElementById('page').innerHTML = result[0].name
* });
*
* * Wait for multiple waitables at once with [FB.Data.waitOn](FB.Data.waitOn).
*
* Check out the [tests][tests] for more usage examples.
* [tests]: http://github.com/facebook/connect-js/blob/master/tests/js/data.js
*
* @class FB.Data.Query
* @access public
* @extends FB.Waitable
*/
FB.subclass('Data.Query', 'Waitable',
function() {
if (!FB.Data.Query._c) {
FB.Data.Query._c = 1;
}
this.name = 'v_' + FB.Data.Query._c++;
},
{
/**
* Use the array of arguments using the FB.String.format syntax to build a
* query, parse it and populate this Query instance.
*
* @params args
*/
parse: function(args) {
var
fql = FB.String.format.apply(null, args),
re = (/^select (.*?) from (\w+)\s+where (.*)$/i).exec(fql); // Parse it
this.fields = this._toFields(re[1]);
this.table = re[2];
this.where = this._parseWhere(re[3]);
for (var i=1; i < args.length; i++) {
if (FB.Type.isType(args[i], FB.Data.Query)) {
// Indicate this query can not be merged because
// others depend on it.
args[i].hasDependency = true;
}
}
return this;
},
/**
* Renders the query in FQL format.
*
* @return {String} FQL statement for this query
*/
toFql: function() {
var s = 'select ' + this.fields.join(',') + ' from ' +
this.table + ' where ';
switch (this.where.type) {
case 'unknown':
s += this.where.value;
break;
case 'index':
s += this.where.key + '=' + this._encode(this.where.value);
break;
case 'in':
if (this.where.value.length == 1) {
s += this.where.key + '=' + this._encode(this.where.value[0]);
} else {
s += this.where.key + ' in (' +
FB.Array.map(this.where.value, this._encode).join(',') + ')';
}
break;
}
return s;
},
/**
* Encode a given value for use in a query string.
*
* @param value {Object} the value to encode
* @returns {String} the encoded value
*/
_encode: function(value) {
return typeof(value) == 'string' ? FB.String.quote(value) : value;
},
/**
* Return the name for this query.
*
* TODO should this be renamed?
*
* @returns {String} the name
*/
toString: function() {
return '#' + this.name;
},
/**
* Return an Array of field names extracted from a given string. The string
* here is a comma separated list of fields from a FQL query.
*
* Example:
* query._toFields('abc, def, ghi ,klm')
* Returns:
* ['abc', 'def', 'ghi', 'klm']
*
* @param s {String} the field selection string
* @returns {Array} the fields
*/
_toFields: function(s) {
return FB.Array.map(s.split(','), FB.String.trim);
},
/**
* Parse the where clause from a FQL query.
*
* @param s {String} the where clause
* @returns {Object} parsed where clause
*/
_parseWhere: function(s) {
// First check if the where is of pattern
// key = XYZ
var
re = (/^\s*(\w+)\s*=\s*(.*)\s*$/i).exec(s),
result,
value,
type = 'unknown';
if (re) {
// Now check if XYZ is either an number or string.
value = re[2];
// The RegEx expression for checking quoted string
// is from http://blog.stevenlevithan.com/archives/match-quoted-string
if (/^(["'])(?:\\?.)*?\1$/.test(value)) {
// Use eval to unquote the string
// convert
value = eval(value);
type = 'index';
} else if (/^\d+\.?\d*$/.test(value)) {
type = 'index';
}
}
if (type == 'index') {
// a simple <key>=<value> clause
result = { type: 'index', key: re[1], value: value };
} else {
// Not a simple <key>=<value> clause
result = { type: 'unknown', value: s };
}
return result;
}
});
/**
* 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.data
* @layer data
* @requires fb.prelude
* fb.type
* fb.api
* fb.array
* fb.string
* fb.obj
* fb.data.query
* fb.json
*/
/**
* Data access class for accessing Facebook data efficiently.
*
* FB.Data is a data layer that offers the following advantages over
* direct use of FB.Api:
*
* 1. Reduce number of individual HTTP requests through the following
* optimizations:
*
* a. Automatically combine individual data requests into a single
* multi-query request.
*
* b. Automatic query optimization.
*
* c. Enable caching of data through browser local cache (not implemented yet)
*
* 2. Reduce complexity of asynchronous API programming, especially multiple
* asynchronous request, though FB.Waitable and FB.waitOn.
*
* @class FB.Data
* @access public
* @static
*/
FB.provide('Data', {
/**
* Performs a parameterized FQL query and returns a [FB.Data.query](FB.Data.query)
* object which can be waited on for the asynchronously fetched data.
*
* Examples
* --------
*
* Make a simple FQL call and handle the results.
*
* var query = FB.Data.query('select name, uid from user where uid={0}',
* user_id);
* query.wait(function(rows) {
* document.getElementById('name').innerHTML =
* 'Your name is ' + rows[0].name;
* });
*
* Display the names and events of 10 random friends. This can't be done
* using a simple FQL query because you need more than one field from more
* than one table, so we use FB.Data.query to help construct the call to
* [[api:fql.multiquery]].
*
* // First, get ten of the logged-in user's friends and the events they
* // are attending. In this query, the argument is just an int value
* // (the logged-in user id). Note, we are not firing the query yet.
* var query = FB.Data.query(
* "select uid, eid from event_member "
* + "where uid in "
* + "(select uid2 from friend where uid1 = {0}"
* + " order by rand() limit 10)",
* user_id);
*
* // Now, construct two dependent queries - one each to get the
* // names of the friends and the events referenced
* var friends = FB.Data.query(
* "select uid, name from user where uid in "
* + "(select uid from {0})", query);
* var events = FB.Data.query(
* "select eid, name from event where eid in "
* + " (select eid from {0})", query);
*
* // Now, register a callback which will execute once all three
* // queries return with data
* FB.Data.waitOn([query, friends, events], function() {
* // build a map of eid, uid to name
* var eventNames = friendNames = {};
* FB.Array.forEach(events.value, function(row) {
* eventNames[row.eid] = row.name;
* });
* FB.Array.forEach(friends.value, function(row) {
* friendNames[row.uid] = row.name;
* });
*
* // now display all the results
* var html = '';
* FB.Array.forEach(query.value, function(row) {
* html += '<p>'
* + friendNames[row.uid]
* + ' is attending '
* + eventNames[row.eid]
* + '</p>';
* });
* document.getElementById('display').innerHTML = html;
* });
*
* @param {String} template FQL query string template. It can contains
* optional formatted parameters in the format of '{<argument-index>}'.
* @param {Object} data optional 0-n arguments of data. The arguments can be
* either real data (String or Integer) or an [FB.Data.query](FB.Data.query)
* object from a previos [FB.Data.query](FB.Data.query).
* @return {FB.Data.Query}
* An async query object that contains query result.
*/
query: function(template, data) {
var query = new FB.Data.Query().parse(arguments);
FB.Data.queue.push(query);
FB.Data._waitToProcess();
return query;
},
/**
* Wait until the results of all queries are ready. See also
* [FB.Data.query](FB.Data.query) for more examples of usage.
*
* Examples
* --------
*
* Wait for several queries to be ready, then perform some action:
*
* var queryTemplate = 'select name from profile where id={0}';
* var u1 = FB.Data.query(queryTemplate, 4);
* var u2 = FB.Data.query(queryTemplate, 1160);
* FB.Data.waitOn([u1, u2], function(args) {
* log('u1 value = '+ args[0].value);
* log('u2 value = '+ args[1].value);
* });
*
* Same as above, except we take advantage of JavaScript closures to
* avoid using args[0], args[1], etc:
*
* var queryTemplate = 'select name from profile where id={0}';
* var u1 = FB.Data.query(queryTemplate, 4);
* var u2 = FB.Data.query(queryTemplate, 1160);
* FB.Data.waitOn([u1, u2], function(args) {
* log('u1 value = '+ u1.value);
* log('u2 value = '+ u2.value);
* });
*
* Create a new Waitable that computes its value based on other Waitables:
*
* var friends = FB.Data.query('select uid2 from friend ' +
* 'where uid1=me()');
* // ...
* // Create a Waitable that is the count of friends
* var count = FB.Data.waitOn([friends], 'args[0].length');
* displayFriendsCount(count);
* // ...
* function displayFriendsCount(count) {
* count.wait(function(result) {
* log('friends count = ' + result);
* });
* }
*
* You can mix Waitables and data in the list of dependencies
* as well.
*
* var queryTemplate = 'select name from profile where id={0}';
* var u1 = FB.Data.query(queryTemplate, 4);
* var u2 = FB.Data.query(queryTemplate, 1160);
*
* FB.Data.waitOn([u1, u2, FB.getUserID()], function(args) {
* log('u1 = '+ args[0]);
* log('u2 = '+ args[1]);
* log('uid = '+ args[2]);
* });
*
* @param dependencies {Array} an array of dependencies to wait on. Each item
* could be a Waitable object or actual value.
* @param callback {Function} A function callback that will be invoked
* when all the data are ready. An array of ready data will be
* passed to the callback. If a string is passed, it will
* be evaluted as a JavaScript string.
* @return {FB.Waitable} A Waitable object that will be set with the return
* value of callback function.
*/
waitOn: function(dependencies, callback) {
var
result = new FB.Waitable(),
count = dependencies.length;
// For developer convenience, we allow the callback
// to be a string of javascript expression
if (typeof(callback) == 'string') {
var s = callback;
callback = function(args) {
return eval(s);
};
}
FB.Array.forEach(dependencies, function(item) {
item.monitor('value', function() {
var done = false;
if (FB.Data._getValue(item) !== undefined) {
count--;
done = true;
}
if (count === 0) {
var value = callback(FB.Array.map(dependencies, FB.Data._getValue));
result.set(value !== undefined ? value : true);
}
return done;
});
});
return result;
},
/**
* Helper method to get value from Waitable or return self.
*
* @param item {FB.Waitable|Object} potential Waitable object
* @returns {Object} the value
*/
_getValue: function(item) {
return FB.Type.isType(item, FB.Waitable) ? item.value : item;
},
/**
* Alternate method from query, this method is more specific but more
* efficient. We use it internally.
*
* @access private
* @param fields {Array} the array of fields to select
* @param table {String} the table name
* @param name {String} the key name
* @param value {Object} the key value
* @returns {FB.Data.Query} the query object
*/
_selectByIndex: function(fields, table, name, value) {
var query = new FB.Data.Query();
query.fields = fields;
query.table = table;
query.where = { type: 'index', key: name, value: value };
FB.Data.queue.push(query);
FB.Data._waitToProcess();
return query;
},
/**
* Set up a short timer to ensure that we process all requests at once. If
* the timer is already set then ignore.
*/
_waitToProcess: function() {
if (FB.Data.timer < 0) {
FB.Data.timer = setTimeout(FB.Data._process, 10);
}
},
/**
* Process the current queue.
*/
_process: function() {
FB.Data.timer = -1;
var
mqueries = {},
q = FB.Data.queue;
FB.Data.queue = [];
for (var i=0; i < q.length; i++) {
var item = q[i];
if (item.where.type == 'index' && !item.hasDependency) {
FB.Data._mergeIndexQuery(item, mqueries);
} else {
mqueries[item.name] = item;
}
}
// Now make a single multi-query API call
var params = { q : {} };
FB.copy(params.q, mqueries, true, function(query) {
return query.toFql();
});
params.queries = FB.JSON.stringify(params.queries);
FB.api('/fql', 'GET', params,
function(result) {
if (result.error) {
FB.Array.forEach(mqueries, function(q1) {
q1.error(new Error(result.error.message));
});
} else {
FB.Array.forEach(result.data, function(o) {
mqueries[o.name].set(o.fql_result_set);
});
}
}
);
},
/**
* Check if y can be merged into x
* @private
*/
_mergeIndexQuery: function(item, mqueries) {
var key = item.where.key,
value = item.where.value;
var name = 'index_' + item.table + '_' + key;
var master = mqueries[name];
if (!master) {
master = mqueries[name] = new FB.Data.Query();
master.fields = [key];
master.table = item.table;
master.where = {type: 'in', key: key, value: []};
}
// Merge fields
FB.Array.merge(master.fields, item.fields);
FB.Array.merge(master.where.value, [value]);
// Link data from master to item
master.wait(function(r) {
item.set(FB.Array.filter(r, function(x) {
return x[key] == value;
}));
});
},
timer: -1,
queue: []
});
/**
* 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.init.helper
* @requires fb.init
* fb.qs
* fb.array
*/
// 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() {
// this is useful to enable fragment based initialization to reduce the
// amount of code needed to perform the common initialization logic
var pattern = /(connect.facebook.net|facebook.com\/assets.php).*?#(.*)/;
FB.Array.forEach(document.getElementsByTagName('script'), function(script) {
if (script.src) {
var match = pattern.exec(script.src);
if (match) {
var opts = FB.QS.decode(match[2]);
FB.Array.forEach(opts, function(val, key) {
if (val == '0') {
opts[key] = 0;
}
});
opts.oauth = true;
FB.init(opts);
}
}
});
// this is useful when the library is being loaded asynchronously
if (window.fbAsyncInit && !window.fbAsyncInit.hasRun) {
window.fbAsyncInit.hasRun = true;
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.
*
* @provides fb.nativecalls
* @requires fb.prelude
* fb.ua
*/
/**
* Provides a way to make native calls to a container Facebook iPhone or
* Android application from Javascript. Used by Mobile Canvas apps. Functions
* provided are:
*
* FB.Native.open(url) - takes a HTTP or HTTPS URL to be opened in the
* popup webview. Returns an object that implements a close() method closing
* the popup webview.
*
* FB.Native.postMessage(message, target) - sends the provided 'message' to
* the specified 'target' window, analagous to javascript's postMessage. Used
* for communication between two webviews (i.e. the canvas webview and a popup
* opened using FB.Native.open).
*
* FB.Native.setOrientation(orientation) (Deprecated - use the developer
* setting for orientation instead). Takes a string parameter that is either
* "portrait" or "landscape", and locks the screen in the specified orientation
*
* Since the native JS injection happens asynchronously, all native functions
* should be called within the callback to FB.Native.onready.
* Example:
* FB.Native.onready(function () {
* var popupWindow = FB.Native.open("http://facebook.com");
* popupWindow.close();
* });
*
*/
FB.provide('Native', {
NATIVE_READY_EVENT: 'fbNativeReady',
/**
* Takes a callback function as a parameter and executes it once the native
* container has injected the javascript functions to make native calls.
*
* (This is necessary since native JS injection happens asynchronously)
*/
onready: function(func) {
// Check that we're within a native container
if (!FB.UA.nativeApp()) {
FB.log('FB.Native.onready only works when the page is rendered ' +
'in a WebView of the native Facebook app. Test if this is the ' +
'case calling FB.UA.nativeApp()');
return;
}
// if the native container has injected JS but we haven't copied it
// into the FB.Native namespace, do that now. This way, all caller
// functions can use methods like FB.Native.open and not have to care
// about window.__fbNative
if (window.__fbNative && !this.nativeReady) {
FB.provide('Native', window.__fbNative);
}
// This will evaluate to true once the native app injects the JS methods
if (this.nativeReady) {
func();
} else {
// If the native interfaces haven't been injected yet,
// wait for an event to fire.
var nativeReadyCallback = function(evt) {
window.removeEventListener(FB.Native.NATIVE_READY_EVENT,
nativeReadyCallback);
FB.Native.onready(func);
};
window.addEventListener(FB.Native.NATIVE_READY_EVENT,
nativeReadyCallback,
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.
*
* @provides fb.pay
* @requires fb.prelude
* fb.arbiter
* fb.json
* fb.ui
* fb.xd
*/
/**
* Implementation of the payment flow UI initiation
*/
FB.provide('UIServer.Methods', {
'pay.prompt': {
transform : function(call) {
var handler = FB.XD.handler(function(msg) {
call.cb(FB.JSON.parse(msg.response));
}, 'parent.frames[' + (window.name || 'iframe_canvas') + ']');
call.params.channel = handler;
FB.Arbiter.inform('Pay.Prompt', call.params);
return false;
}
}
});
FB.provide('UIServer.Methods', {
'pay': {
size : { width: 555, height: 120 },
noHttps : true,
connectDisplay : 'popup',
transform : function(call) {
if (!FB._inCanvas) {
// Hack to keep backward compatibility
call.params.order_info = FB.JSON.stringify(call.params.order_info);
return call;
}
var handler = FB.XD.handler(function(msg) {
call.cb(FB.JSON.parse(msg.response));
}, 'parent.frames[' + (window.name || 'iframe_canvas') + ']');
call.params.channel = handler;
call.params.uiserver = true;
FB.Arbiter.inform('Pay.Prompt', call.params);
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.
*
* @provides fb.helper
* @layer xfbml
* @requires fb.prelude
*/
/**
* Helper class for XFBML
* @class FB.Helper
* @static
* @private
*/
FB.provide('Helper', {
/**
* Check if an id is an user id, instead of a page id
*
* [NOTE:] This code is based on fbid_in_uid_range function in our server code
* If that function changes, we'd have to update this one as well.
*
* @param {uid} id
* @returns {Boolean} true if the given id is a user id
*/
isUser: function(id) {
return id < 2200000000 ||
(id >= 100000000000000 && // 100T is first 64-bit UID
id <= 100099999989999) || // 100T + 3,333,333*30,000 - 1)
(id >= 89000000000000 && // DBTYPE_TEST2: see flib/core/fbid/hash.php
id <= 89999999999999);
},
/**
* Return the current user's UID if available.
*
* @returns {String|Number} returns the current user's UID or null
*/
getLoggedInUser: function() {
return FB.getUserID(); // pass the buck to the auth response
},
/**
* Uppercase the first character of the String.
*
* @param s {String} the string
* @return {String} the string with an uppercase first character
*/
upperCaseFirstChar: function(s) {
if (s.length > 0) {
return s.substr(0, 1).toUpperCase() + s.substr(1);
}
else {
return s;
}
},
/**
* Link to the explicit href or profile.php.
*
* @param userInfo {FB.UserInfo} User info object.
* @param html {String} Markup for the anchor tag.
* @param href {String} Custom href.
* @returns {String} the anchor tag markup
*/
getProfileLink: function(userInfo, html, href) {
href = href || (userInfo ? FB.getDomain('www') + 'profile.php?id=' +
userInfo.uid : null);
if (href) {
html = '<a class="fb_link" href="' + href + '">' + html + '</a>';
}
return html;
},
/**
* Convenienve function to fire an event handler attribute value. This is a
* no-op for falsy values, eval for strings and invoke for functions.
*
* @param handler {Object}
* @param scope {Object}
* @param args {Array}
*/
invokeHandler: function(handler, scope, args) {
if (handler) {
if (typeof handler === 'string') {
eval(handler);
} else if (handler.apply) {
handler.apply(scope, args || []);
}
}
},
/**
* Convenience function that fires the given event using both
* FB.Helper.fire and the prototype fire method. Passes the
* event raiser's href attriubute as an argument.
*
* @param evenName {String}
* @param eventRaiser {Object}
*/
fireEvent: function(eventName, eventSource) {
var href = eventSource._attr.href;
eventSource.fire(eventName, href); // dynamically attached
FB.Event.fire(eventName, href, eventSource); // global
},
/**
* Converts a string to a function without using eval()
*
* From http://stackoverflow.com/questions/359788
*/
executeFunctionByName: function(functionName /*, args */) {
var args = Array.prototype.slice.call(arguments, 1);
var namespaces = functionName.split(".");
var func = namespaces.pop();
var context = window;
for (var i = 0; i < namespaces.length; i++) {
context = context[namespaces[i]];
}
return context[func].apply(this, 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.
*
*
*
* JavaScript library providing Facebook Connect integration.
*
* @provides fb.template_data
* @requires fb.api
* fb.json
* fb.helper
*/
/**
* For preview templates, we need some per-app, per-user data that we fetch
* asynchronosly and cache in local browser storage for as long as possible.
* The events that might cause a local storage update are logging in or out
* and a periodical timeout.
*
* @class FB.TemplateData
* @static
* @access private
*/
FB.provide('TemplateData', {
_initialized: false,
_version: 0,
_response: null,
_localStorageTimeout: 60 * 60 * 24,
// Set in ConnectStaticResponse as a temporary emergency
// tool until we're sure most Akamai-related issues are
// known to us.
_enabled: true,
enabled: function() {
return FB.TemplateData._enabled &&
FB.TemplateData._initialized &&
FB.TemplateData.supportsLocalStorage() &&
FB._userStatus == 'connected' &&
FB.TemplateData.getResponse();
},
supportsLocalStorage: function() {
try {
return 'localStorage' in window && window.localStorage !== null;
} catch (e) {
// Bug in old Firefox versions for disabled cookies
return false;
}
},
/**
* True if all of these are met:
* - there's a response in localStorage
* - it was set soon enough
* - the version is up to date
*/
_isStale: function(response) {
if (!response || !response.version ||
response.version != FB.TemplateData._version ||
response.currentUserID != FB.getUserID()) {
return true;
}
var currentTime = Math.round((new Date()).getTime());
return (currentTime - response.setAt) / 1000.0 >
FB.TemplateData._localStorageTimeout;
},
getResponse: function() {
var self = FB.TemplateData;
try {
self._response = self._response ||
(self.supportsLocalStorage() &&
FB.JSON.parse(localStorage.FB_templateDataResponse || "null"));
} catch (e) {
// Catch possible bad data in localStorage
self._response = null;
}
if (self._isStale(self._response)) {
self.saveResponse(null);
}
return self._response;
},
saveResponse: function(response) {
FB.TemplateData._response = response;
if (FB.TemplateData.supportsLocalStorage()) {
localStorage.FB_templateDataResponse = FB.JSON.stringify(response);
}
},
/**
* Returns the data in FB_templateDataResponse or {}
* if one hasn't been loaded yet.
*/
getData: function() {
var response = FB.TemplateData.getResponse();
return response ? response.data : {};
},
init: function(version) {
if (!version) {
return;
}
FB.TemplateData._initialized = true;
FB.TemplateData._version = version;
if (FB.TemplateData.supportsLocalStorage() &&
!('FB_templateDataResponse' in localStorage)) {
FB.TemplateData.clear();
}
},
clear: function() {
FB.TemplateData.saveResponse(null);
},
/**
* Called on auth.statusChange.
* Updates the state of this module as appropriate.
* Assumes init() has been called.
*/
update: function(loginStatusResponse) {
if (FB._userStatus != 'connected') {
FB.TemplateData.clear();
}
if (FB._userStatus == 'connected' &&
!FB.TemplateData.getResponse()) {
FB.api({ method: 'dialog.template_data'}, function(response) {
if ('error_code' in response) {
// Something went wrong
return;
}
var data = {
data: response,
currentUserID: FB.getUserID(),
setAt: (new Date()).getTime(),
version: FB.TemplateData._version};
FB.TemplateData.saveResponse(data);
});
}
}
});
/**
* 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.template_ui
* @requires fb.type
* fb.obj
* fb.template_data
* fb.ua
* fb.ui
*/
/**
* Provide Support for Template UI dialog
*
* @class FB.TemplateUI
* @access private
*/
FB.subclass('TemplateUI', 'Obj',
// Constructor
function(method, isPreload) {
this.method = method;
var in_iframe = FB.UA.nativeApp() ? 0 : 1;
var query_params =
{display: 'touch',
preview_template: 1,
in_iframe: in_iframe,
locale: FB._locale,
v: FB.TemplateUI._version,
user_agent: navigator.userAgent
};
if (window.devicePixelRatio) {
query_params.m_pixel_ratio = window.devicePixelRatio;
}
var query_string = FB.QS.encode(query_params);
// Create a dialog that points to akamai cached template
// ui dialog, then hide the dialog, so that it may be used
// later.
this.cachedCall = {
url: FB.getDomain('staticfb') + 'dialog/' + method + '?' + query_string,
frameName: FB.guid(),
id: FB.guid(),
size: FB.UIServer.getDefaultSize(),
hideLoader: true
};
// A Mobile Safari bug prevents pages from caching even if only fragment in
// a url changes, so we use cross-domain communication to pass data
// parameters. This also means the parameter size won't be limited browser's
// maximum url length.
FB.XD.handler(this.bind(function(data) {
if (data.type == 'getParams') {
// store returnCb for later when the cached dialog is
// used.
this.setProperty('getParamsCb', data.returnCb);
}
}), 'parent', true, this.cachedCall.frameName);
// Create an iframe first, then hide the dialog. This allows us to effectively
// hide the time it takes for a browser to parse and render a page.
// On iPhone 3GS, that can be 500ms.
// Note currently we cannot pre-load for popup and native dialogs because
// we can't create them in hidden mode.
if (in_iframe) {
FB.UIServer.iframe(this.cachedCall);
FB.Dialog.hide(this.cachedCall.root);
} else if (isPreload && !FB.TemplateUI._preloads[this.cachedCall.url]) {
// For now, we don't have a good way to preload template dialog inside
// native app because we don't create a hidden dialog and show it
// later.
// However, if we create a hidden iframe to the same url, we'd at least
// be able to pre-fetch resources from Akamai in case of cold cache since
// template dialog is completedly static and cacheable.
var container = document.createElement('div');
FB.TemplateUI._preloads[this.cachedCall.url] = {container: container};
FB.Content.insertIframe({
url: this.cachedCall.url,
root: FB.Content.appendHidden(container)
});
}
},
// Instance Methods
{
/**
* Use the template UI
* @access private
* @param call {Object} call parameters
*/
use: function(call) {
if (!this.cachedCall.root) {
FB.UIServer.touch(this.cachedCall);
// Check if there is an iframe that was used to preload
// the same url. If so, remove it because we don't need it
// anymore
var preload = FB.TemplateUI._preloads[this.cachedCall.url];
if (preload && preload.container) {
preload.container.parentNode.removeChild(preload.container);
delete preload.container;
}
}
call.ui_created = true;
// Set dialog root to that of the cached one.
call.root = this.cachedCall.root;
// Switch any place where cached call id is used.
// Absolutely terrible. Needs refactoring.
// FB.UIServer._loadedNodes will keep the new id, which
// is not related to the DOM in any way.
FB.UIServer.setLoadedNode(call,
FB.UIServer.getLoadedNode(this.cachedCall.id));
delete FB.UIServer._loadedNodes[this.cachedCall.id];
// FB.Dialog._dialogs and FB.Dialog._loadedNodes[frame].fbCallID
// will keep the real iframe's id
// because that's used to later resize the iframes.
var dialog = FB.Dialog._dialogs[call.id];
FB.Dialog._dialogs[this.cachedCall.id] = dialog;
dialog.id = this.cachedCall.id;
delete FB.Dialog._dialogs[call.id];
FB.UIServer.getLoadedNode(call).fbCallID = this.cachedCall.id;
this.cachedCall.id = call.id;
var template_params = {};
FB.copy(template_params, call.params);
FB.copy(template_params, FB.TemplateData.getData()[this.method]);
template_params.frictionless =
FB.TemplateUI.isFrictionlessAppRequest(this.method, template_params);
template_params.common = FB.TemplateData.getData().common;
template_params.method = this.method;
this.setParams(template_params);
// Note that the same check in FB.UIServer.touch covers the regular version
// of the dialog, and this one covers the template version. This is because
// the default cb is not associated with the template dialog until
// FB.TemplateUI.use() is called.
if (FB.UA.nativeApp()) {
FB.UIServer._popupMonitor();
}
},
/**
* Use postMessage to pass data parameter to template iframe
* @access private
* @param params {Object} data parametes
*/
setParams: function(params) {
// We need to wait until the iframe send callback cb
// for getParams
this.monitor('getParamsCb', this.bind(function() {
if (this.getParamsCb) {
var dialogWindow = frames[this.cachedCall.frameName] ||
FB.UIServer.getLoadedNode(this.cachedCall);
dialogWindow.postMessage(FB.JSON.stringify(
{params: params,
cb: this.getParamsCb
}), '*');
return true;
}
}));
}
});
// Static methods
FB.provide('TemplateUI', {
_timer: null,
_cache: {},
_preloads: {},
// Overridden by the PLATFORM_DIALOG_TEMPLATE_VERSION sitevar.
// A value of 0 disables templates.
_version: 0,
/**
* Initialization function.
*/
init: function() {
FB.TemplateData.init(FB.TemplateUI._version);
FB.TemplateUI.initCache();
},
/**
* Use cached UI for dialog
* @param method {string} method name
* @param call {call} call parameters
*/
useCachedUI: function(method, call) {
try {
// Ensure the relevant iframe is rendered. Usually a no-op.
FB.TemplateUI.populateCache();
cache = FB.TemplateUI._cache[method];
// Ensure we don't try to reuse the same iframe later
delete FB.TemplateUI._cache[method];
cache.use(call);
} catch (e) {
// To prevent an eternally broken state
// caused by completely unexpected data-related errors.
FB.TemplateData.clear();
}
},
/**
* Will prerender any iframes not already in
* FB.TemplateUI._cache. Called at init time and after
* a cached iframe is closed.
*/
populateCache: function(isPreload) {
if (!FB.TemplateData.enabled() || !FB.UA.mobile()) {
return;
}
clearInterval(FB.TemplateUI._timer);
var methods = {feed: true, apprequests: true};
for (var method in methods) {
if (!(method in FB.TemplateUI._cache)) {
FB.TemplateUI._cache[method] = new FB.TemplateUI(method, isPreload);
}
}
},
/**
* We use a timer to check and initialize cached UI for two
* reasons:
* 1. Try to delay the loading of the cached UI to minimize impact
* on application
* 2. If the template data is not ready, we need to wait for it.
*/
initCache: function() {
FB.TemplateUI._timer = setInterval(function() {
FB.TemplateUI.populateCache(true);
}, 2000);
},
/**
* Only the feed and apprequests dialogs run template versions
* when possible, though some of their features can't be or aren't
* implemented with templates yet.
*/
supportsTemplate: function(method, call) {
return FB.TemplateData.enabled() &&
FB.TemplateUI.paramsAllowTemplate(method, call.params) &&
call.params.display === 'touch' &&
FB.UA.mobile();
},
/**
* Feed templates don't support these:
* - dialogs posting to a friend's wall
* - deprecated attachment params
* - source, which means video
* App Request templates don't support these:
* - pre-specified friend
* - suggestions
* @param method {String} method name
* @param app_params {Object} the FB.ui parameters
* @return {Boolean} whether the call can use a template dialog
*/
paramsAllowTemplate: function(method, app_params) {
var bad_params =
{feed: {to: 1, attachment: 1, source: 1},
apprequests: {}};
if (!(method in bad_params)) {
return false;
}
for (var param in bad_params[method]) {
if (app_params[param]) {
return false;
}
}
return !FB.TemplateUI.willWriteOnGet(method, app_params);
},
isFrictionlessAppRequest: function(method, app_params) {
return method === 'apprequests' && FB.Frictionless &&
FB.Frictionless._useFrictionless;
},
/**
* Frictionless requests kick off the full-param version
* of the dialog if enabled for the specified recipient
* because they send out the notification on the first
* server request.
* @param method {String} method name
* @param app_params {Object} the FB.ui parameters
* @return {Boolean} true if a regular call would
* write to the DB on the first request
*/
willWriteOnGet: function(method, app_params) {
return FB.TemplateUI.isFrictionlessAppRequest(method, app_params) &&
app_params.to &&
FB.Frictionless.isAllowed(app_params.to);
}
});
/**
* 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.uri
* @requires fb.ua
*/
/**
* URI Handling
*/
FB.provide('URI', {
/**
* Resolve a relative URL to an absolute URL. An absolute URL will resolve to
* itself. The empty string resolves to the current window location.
*/
resolve: function(uri) {
if (!uri) { // IE handles this case poorly, so we will be explicit about it
return window.location.href;
}
var div = document.createElement('div');
// This uses `innerHTML` because anything else doesn't resolve properly or
// causes an HTTP request in IE6/7.
div.innerHTML = '<a href="' + uri.replace(/"/g, '&quot;') + '"></a>';
return div.firstChild.href; // This will be an absolute URL. MAGIC!
}
});
/**
* 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.xfbml.element
* @layer xfbml
* @requires fb.type fb.event fb.array
*/
/**
* Base class for all XFBML elements. To create your own XFBML element, make a
* class that derives from this, and then call [FB.XFBML.registerTag](FB.XFBML.registerTag).
*
* @access private
* @class FB.XFBML.Element
*/
FB.Class('XFBML.Element',
/**
* Create a new Element.
*
* @access private
* @constructor
* @param dom {DOMElement} the DOMElement for the tag
*/
function(dom) {
this.dom = dom;
},
FB.copy({
/**
* Get the value of an attribute associated with this tag.
*
* Note, the transform function is never executed over the default value. It
* is only used to transform user set attribute values.
*
* @access private
* @param name {String} Name of the attribute.
* @param defaultValue {Object} Default value if attribute isn't set.
* @param transform {Function} Optional function to transform found value.
* @return {Object} final value
*/
getAttribute: function(name, defaultValue, transform) {
var value = FB.XFBML.getAttr(this.dom, name);
return value ? (transform ? transform(value) : value) : defaultValue;
},
/**
* Helper function to extract boolean attribute value.
*
* @access private
* @param name {String} Name of the attribute.
* @param defaultValue {Object} Default value if attribute isn't set.
*/
_getBoolAttribute: function(name, defaultValue) {
if (FB.XFBML.getAttr(this.dom, name) === null) {
return defaultValue;
}
return FB.XFBML.getBoolAttr(this.dom, name);
},
/**
* Get an integer value for size in pixels.
*
* @access private
* @param name {String} Name of the attribute.
* @param defaultValue {Object} Default value if attribute isn't set.
*/
_getPxAttribute: function(name, defaultValue) {
return this.getAttribute(name, defaultValue, function(s) {
var size = parseInt(s.replace('px', ''), 10);
if (isNaN(size)) {
return defaultValue;
} else {
return size;
}
});
},
/**
* Get a value if it is in the allowed list, otherwise return the default
* value. This function ignores case and expects you to use only lower case
* allowed values.
*
* @access private
* @param name {String} Name of the attribute.
* @param defaultValue {Object} Default value
* @param allowed {Array} List of allowed values.
*/
_getAttributeFromList: function(name, defaultValue, allowed) {
return this.getAttribute(name, defaultValue, function(s) {
s = s.toLowerCase();
if (FB.Array.indexOf(allowed, s) > -1) {
return s;
} else {
return defaultValue;
}
});
},
/**
* Check if this node is still valid and in the document.
*
* @access private
* @returns {Boolean} true if element is valid
*/
isValid: function() {
for (var dom = this.dom; dom; dom = dom.parentNode) {
if (dom == document.body) {
return true;
}
}
},
/**
* Clear this element and remove all contained elements.
*
* @access private
*/
clear: function() {
this.dom.innerHTML = '';
}
}, FB.EventProvider));
/**
* @provides fb.xfbml.iframewidget
* @requires fb.arbiter
* fb.content
* fb.event
* fb.qs
* fb.type
* fb.xfbml.element
* @css fb.css.iframewidget
* @layer xfbml
*/
/**
* 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.
*/
/**
* Base implementation for iframe based XFBML Widgets.
*
* @class FB.XFBML.IframeWidget
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.IframeWidget', 'XFBML.Element', null, {
/**
* The name that should be used for the 'name' attribute of
* the iframe. Normally null, which means that it can be auto-generated
* without any regard for convention, but it can be set by the
* subclass if the name is important.
*/
_iframeName: null,
/**
* Indicate if the loading animation should be shown while the iframe is
* loading.
*/
_showLoader: true,
/**
* Indicate if the widget should be reprocessed when the user enters or
* leaves the "unknown" state. (Logs in/out of facebook, but not the
* application.)
*/
_refreshOnAuthChange: false,
/**
* Indicates if the widget should be reprocessed on auth.statusChange events.
* This is the default for XFBML Elements, but is usually undesirable for
* Iframe Widgets.
*/
_allowReProcess: false,
/**
* Indicates if the widget should be loaded from a static response cached
* version the CDN, only needed for high performance widgets that need to
* minimize TTI.
*/
_fetchPreCachedLoader: false,
/**
* Indicates when the widget will be made visible.
*
* load: when the iframe's page onload event is fired
* resize: when the first resize message is received
* immediate: when there is any HTML.
*/
_visibleAfter: 'load',
/**
* Indicates whether or not the widget should be rendered using
* WidgetPipe. The default is false, but this same field should
* be set to true if the IframeWidget subclass has WidgetPipe support.
*/
_widgetPipeEnabled: false,
/////////////////////////////////////////////////////////////////////////////
// Methods the implementation MUST override
/////////////////////////////////////////////////////////////////////////////
/**
* Implemented by the inheriting class to return a **name** and **params**.
*
* The name is the the file name in the plugins directory. So the name "fan"
* translates to the path "/plugins/fan.php". This enforces consistency.
*
* The params should be the query params needed for the widget. API Key,
* Session Key, SDK and Locale are automatically included.
*
* @return {Object} an object containing a **name** and **params**.
*/
getUrlBits: function() {
throw new Error('Inheriting class needs to implement getUrlBits().');
},
/////////////////////////////////////////////////////////////////////////////
// Methods the implementation CAN override
/////////////////////////////////////////////////////////////////////////////
/**
* This method is invoked before any processing is done to do any initial
* setup and do any necessary validation on the attributes. A return value of
* false will indicate that validation was unsuccessful and processing will
* be halted. If you are going to return false and halt processing, you
* should ensure you use FB.log() to output a short informative message
* before doing so.
*
* @return {Boolean} true to continue processing, false to halt it
*/
setupAndValidate: function() {
return true;
},
/**
* This is useful for setting up event handlers and such which should not be
* run again if the widget is reprocessed.
*/
oneTimeSetup: function() {},
/**
* Implemented by the inheriting class to return the initial size for the
* iframe. If the inheriting class does not implement this, we default to
* null which implies no element level style. This is useful if you are
* defining the size based on the className.
*
* @return {Object} object with a width and height as Numbers (pixels assumed)
*/
getSize: function() {},
/**
* Generates and returns a unique frame name for this particular
* iframe if a name hasn't already been provided *and* the
* widget/plugin is widget-pipe-enabled, so that one needs to
* be generated. If the name has already been generated, or
* if it hasn't but we don't care to generate one because it
* isn't widget-pipe enabled, then we just return what the
* iframe name currently is.
*
* @return {String} the name given to the iframe.
*/
getIframeName: function() {
if (!this._iframeName &&
this._widgetPipeEnabled &&
FB.XFBML.shouldUseWidgetPipe()) {
this._iframeName = this.generateWidgetPipeIframeName();
FB.XFBML.IframeWidget.allWidgetPipeIframes[this._iframeName] = this;
if (FB.XFBML.IframeWidget.masterWidgetPipeIframe === null) {
FB.XFBML.IframeWidget.masterWidgetPipeIframe = this;
}
}
return this._iframeName;
},
/**
* Implemented by a subclass that wants to attach a title as an attribute
* to the top-level iframe tag.
*/
getIframeTitle: function() {},
/////////////////////////////////////////////////////////////////////////////
// Public methods the implementation CAN use
/////////////////////////////////////////////////////////////////////////////
/**
* Get a channel url for use with this widget.
*
* @return {String} the channel URL
*/
getChannelUrl: function() {
if (!this._channelUrl) {
// parent.parent => the message will be going from cdn => fb => app (with
// cdn being the deepest frame, and app being the top frame)
var self = this;
this._channelUrl = FB.XD.handler(function(message) {
self.fire('xd.' + message.type, message);
}, 'parent.parent', true);
}
return this._channelUrl;
},
/**
* Returns the iframe node (if it has already been created).
*
* @return {DOMElement} the iframe DOM element
*/
getIframeNode: function() {
// not caching to allow for the node to change over time without needing
// house-keeping for the cached reference.
return this.dom.getElementsByTagName('iframe')[0];
},
/**
* Call FB.Arbiter.inform with values appropriate to a social plugin embedded
* in a 3rd party site. The `this.loaded` variable is changed in an
* `iframe.onload` callback, so it's state is not guaranteed to be correct in
* the context of an `iframe.onload` event handler. Therefore, this function
* will not necessarily work if called from inside an `iframe.onload` event
* handler.
*/
arbiterInform: function(event, message, behavior) {
if (this.loaded) {
this._arbiterInform(event, message, behavior);
} else {
this.subscribe( // inform once iframe exists
'iframe.onload',
FB.bind(this._arbiterInform, this, event, message, behavior));
}
},
/**
* This is an internal helper to deal with synchronization to prevent race
* conditions in arbiterInform. Clients should use arbiterInform.
*/
_arbiterInform: function(event, message, behavior) {
var relation = 'parent.frames["' + this.getIframeNode().name + '"]';
FB.Arbiter.inform(
event, message, relation, window.location.protocol == 'https:', behavior);
},
/**
* Returns the default domain that should be used for all
* plugins served from our web tier.
*/
getDefaultWebDomain: function() {
return 'www';
},
/**
* Returns the default domain that should be used for all
* plugins served from our Akamai tier.
*/
getDefaultStaticDomain: function() {
return 'cdn';
},
/////////////////////////////////////////////////////////////////////////////
// Private methods the implementation MUST NOT use or override
/////////////////////////////////////////////////////////////////////////////
/**
* Inheriting classes should not touch the DOM directly, and are only allowed
* to override the methods defined at the top.
*
* @param force {Boolean} force reprocessing of the node
*/
process: function(force) {
// guard agains reprocessing if needed
if (this._done) {
if (!this._allowReProcess && !force) {
return;
}
this.clear();
} else {
this._oneTimeSetup();
}
this._done = true;
if (!this.setupAndValidate()) {
// failure to validate means we're done rendering what we can
this.fire('render');
return;
}
// show the loader if needed
if (this._showLoader) {
this._addLoader();
}
// it's always hidden by default
FB.Dom.addCss(this.dom, 'fb_iframe_widget');
if (this._visibleAfter != 'immediate') {
FB.Dom.addCss(this.dom, 'fb_hide_iframes');
} else {
this.subscribe('iframe.onload', FB.bind(this.fire, this, 'render'));
}
// the initial size
var size = this.getSize() || {};
var url = this.getFullyQualifiedURL();
if (size.width == '100%') {
FB.Dom.addCss(this.dom, 'fb_iframe_widget_fluid');
}
FB.Content.insertIframe({
url : url,
root : this.dom.appendChild(document.createElement('span')),
name : this.getIframeName(),
title : this.getIframeTitle(),
className : FB._localeIsRtl ? 'fb_rtl' : 'fb_ltr',
height : size.height,
width : size.width,
onload : FB.bind(this.fire, this, 'iframe.onload')
});
this.loaded = false;
this.subscribe(
'iframe.onload', FB.bind(function() { this.loaded = true; }, this));
},
/**
* The default way we generate iframe names. This (protected)
* method should be overridden by the subclass if it requires
* a different iframe naming convention.
*
* @return {String} the name that should be given to the widget-pipe
* enabled iframe.
*/
generateWidgetPipeIframeName: function() {
FB.XFBML.IframeWidget.widgetPipeIframeCount++;
return 'fb_iframe_' + FB.XFBML.IframeWidget.widgetPipeIframeCount;
},
/**
* Computes the full URL that should be implanted iframe src.
* In the case of short URLs, the traditional GET approach is used,
* but for excessively large URLs we set the URL to be 'about:blank'
* and use POST to get the content after the iframe loads (note the
* subscription to and eventually unsubscription from the iframe.onload
* event. Clever.)
*
* @return {String} the fully qualified, properly encoded URL
*/
getFullyQualifiedURL: function() {
if (FB.XFBML.shouldUseWidgetPipe() && this._widgetPipeEnabled) {
return this._getWidgetPipeShell();
}
// we use a GET request if the URL is less than 2k, otherwise we need to do
// a <form> POST. we prefer a GET because it prevents the "POST resend"
// warning browsers shown on page refresh.
var url = this._getURL();
if (!this._fetchPreCachedLoader) {
url += '?' + FB.QS.encode(this._getQS());
}
if (url.length > 2000) {
// we will POST the form once the empty about:blank iframe is done loading
url = 'about:blank';
var onload = FB.bind(function() {
this._postRequest();
this.unsubscribe('iframe.onload', onload);
}, this);
this.subscribe('iframe.onload', onload);
}
return url;
},
/**
* Identifies the static resource that should be loaded
* if the widget is relying on a separate iframe to fetch
* its content with that of other widget-pipe-enabled widgets.
*
* @return {String} the URL of the widget pipe shell that
* contains the inlined JavaScript invoked when the
* widget pipe iframe has pulled its content and signal
* this iframe that the content is ready.
*/
_getWidgetPipeShell: function() {
return FB.getDomain('www') + 'common/widget_pipe_shell.php';
},
/**
* Internal one time setup logic.
*/
_oneTimeSetup: function() {
// the XD messages we want to handle. it is safe to subscribe to these even
// if they will not get used.
this.subscribe('xd.resize', FB.bind(this._handleResizeMsg, this));
// weak dependency on FB.Auth
if (FB.getLoginStatus) {
this.subscribe(
'xd.refreshLoginStatus',
FB.bind(FB.getLoginStatus, FB, function(){}, true));
this.subscribe(
'xd.logout',
FB.bind(FB.logout, FB, function(){}));
}
// setup forwarding of auth.statusChange events
if (this._refreshOnAuthChange) {
this._setupAuthRefresh();
}
// if we need to make it visible on iframe load
if (this._visibleAfter == 'load') {
this.subscribe('iframe.onload', FB.bind(this._makeVisible, this));
}
// hook for subclasses
this.oneTimeSetup();
},
/**
* Make the iframe visible and remove the loader.
*/
_makeVisible: function() {
this._removeLoader();
FB.Dom.removeCss(this.dom, 'fb_hide_iframes');
this.fire('render');
},
/**
* Most iframe plugins do not tie their internal state to the "Connected"
* state of the application. In other words, the fan box knows who you are
* even if the page it contains does not. These plugins therefore only need
* to reload when the user signs in/out of facebook, not the application.
*
* This misses the case where the user switched logins without the
* application knowing about it. Unfortunately this is not possible/allowed.
*/
_setupAuthRefresh: function() {
FB.getLoginStatus(FB.bind(function(response) {
var lastStatus = response.status;
FB.Event.subscribe('auth.statusChange', FB.bind(function(response) {
if (!this.isValid()) {
return;
}
// if we gained or lost a user, reprocess
if (lastStatus == 'unknown' || response.status == 'unknown') {
this.process(true);
}
lastStatus = response.status;
}, this));
}, this));
},
/**
* Invoked by the iframe when it wants to be resized.
*/
_handleResizeMsg: function(message) {
if (!this.isValid()) {
return;
}
var iframe = this.getIframeNode();
iframe.style.height = message.height + 'px';
if (message.width) {
iframe.style.width = message.width + 'px';
}
iframe.style.border = 'none';
this._makeVisible();
},
/**
* Add the loader.
*/
_addLoader: function() {
if (!this._loaderDiv) {
FB.Dom.addCss(this.dom, 'fb_iframe_widget_loader');
this._loaderDiv = document.createElement('div');
this._loaderDiv.className = 'FB_Loader';
this.dom.appendChild(this._loaderDiv);
}
},
/**
* Remove the loader.
*/
_removeLoader: function() {
if (this._loaderDiv) {
FB.Dom.removeCss(this.dom, 'fb_iframe_widget_loader');
if (this._loaderDiv.parentNode) {
this._loaderDiv.parentNode.removeChild(this._loaderDiv);
}
this._loaderDiv = null;
}
},
/**
* Get's the final QS/Post Data for the iframe with automatic params added
* in.
*
* @return {Object} the params object
*/
_getQS: function() {
return FB.copy({
api_key : FB._apiKey,
locale : FB._locale,
sdk : 'joey',
ref : this.getAttribute('ref')
}, this.getUrlBits().params);
},
/**
* Gets the final URL based on the name specified in the bits.
*
* @return {String} the url
*/
_getURL: function() {
var
domain = this.getDefaultWebDomain(),
static_path = '';
if (this._fetchPreCachedLoader) {
domain = this.getDefaultStaticDomain();
static_path = 'static/';
}
return FB.getDomain(domain) + 'plugins/' + static_path +
this.getUrlBits().name + '.php';
},
/**
* Will do the POST request to the iframe.
*/
_postRequest: function() {
FB.Content.submitToTarget({
url : this._getURL(),
target : this.getIframeNode().name,
params : this._getQS()
});
}
});
FB.provide('XFBML.IframeWidget', {
widgetPipeIframeCount: 0,
masterWidgetPipeIframe: null,
allWidgetPipeIframes: {},
batchWidgetPipeRequests: function() {
if (!FB.XFBML.IframeWidget.masterWidgetPipeIframe) {
// nothing widget-pipe enabled is being rendered,
// so ignore this entirely.
return;
}
var widgetPipeDescriptions =
FB.XFBML.IframeWidget._groupWidgetPipeDescriptions();
var widgetPipeParams = {
widget_pipe: FB.JSON.stringify(widgetPipeDescriptions),
href: window.location,
site: location.hostname,
channel: FB.XFBML.IframeWidget.masterWidgetPipeIframe.getChannelUrl(),
api_key: FB._apiKey,
locale: FB._locale,
sdk: 'joey'
};
var widgetPipeIframeName = FB.guid();
var masterWidgetPipeDom = FB.XFBML.IframeWidget.masterWidgetPipeIframe.dom;
// we need a dedicated span within the first fb:like tag to house the
// iframe that fetches all of the plugin data.
var masterWidgetPipeSpan =
masterWidgetPipeDom.appendChild(document.createElement('span'));
FB.Content.insertIframe({
url: 'about:blank',
root: masterWidgetPipeSpan,
name: widgetPipeIframeName,
className: 'fb_hidden fb_invisible',
onload: function() {
FB.Content.submitToTarget({
url: FB._domain.www + 'widget_pipe.php?widget_pipe=1',
target: widgetPipeIframeName,
params: widgetPipeParams
});
}
});
},
_groupWidgetPipeDescriptions: function() {
var widgetPipeDescriptions = {};
for (var key in FB.XFBML.IframeWidget.allWidgetPipeIframes) {
var controller = FB.XFBML.IframeWidget.allWidgetPipeIframes[key];
var urlBits = controller.getUrlBits();
var widgetPipeDescription = {
widget: urlBits.name
};
FB.copy(widgetPipeDescription, urlBits.params);
widgetPipeDescriptions[key] = widgetPipeDescription;
}
return widgetPipeDescriptions;
}
});
/**
* 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.xfbml.activity
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget
*/
/**
* Implementation for fb:activity tag.
*
* @class FB.XFBML.Activity
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Activity', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
/**
* Refresh the iframe on auth.statusChange events.
*/
_refreshOnAuthChange: true,
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
border_color : this.getAttribute('border-color'),
colorscheme : this.getAttribute('color-scheme'),
filter : this.getAttribute('filter'),
action : this.getAttribute('action'),
max_age : this.getAttribute('max_age'),
font : this.getAttribute('font'),
linktarget : this.getAttribute('linktarget', '_blank'),
header : this._getBoolAttribute('header'),
height : this._getPxAttribute('height', 300),
recommendations : this._getBoolAttribute('recommendations'),
site : this.getAttribute('site', location.hostname),
width : this._getPxAttribute('width', 300)
};
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'activity', params: this._attr };
}
});
/**
* @provides fb.xfbml.buttonelement
* @requires fb.string
* fb.type
* fb.xfbml.element
* @css fb.css.button
* @layer xfbml
*/
/**
* 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.
*/
/**
* Base class for a button element.
*
* @class FB.XFBML.ButtonElement
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.ButtonElement', 'XFBML.Element', null, {
_allowedSizes: ['icon', 'small', 'medium', 'large', 'xlarge'],
/////////////////////////////////////////////////////////////////////////////
// Methods the implementation MUST override
/////////////////////////////////////////////////////////////////////////////
/**
* Invoked when the button is clicked.
*/
onClick: function() {
throw new Error('Inheriting class needs to implement onClick().');
},
/////////////////////////////////////////////////////////////////////////////
// Methods the implementation CAN override
/////////////////////////////////////////////////////////////////////////////
/**
* This method is invoked before any processing is done to do any initial
* setup and do any necessary validation on the attributes. A return value of
* false will indicate that validation was unsuccessful and processing will
* be halted. If you are going to return false and halt processing, you
* should ensure you use FB.log() to output a short informative message
* before doing so.
*
* @return {Boolean} true to continue processing, false to halt it
*/
setupAndValidate: function() {
return true;
},
/**
* Should return the button markup. The default behaviour is to return the
* original innerHTML of the element.
*
* @return {String} the HTML markup for the button
*/
getButtonMarkup: function() {
return this.getOriginalHTML();
},
/////////////////////////////////////////////////////////////////////////////
// Public methods the implementation CAN use
/////////////////////////////////////////////////////////////////////////////
/**
* Get the original innerHTML of the element.
*
* @return {String} the original innerHTML
*/
getOriginalHTML: function() {
return this._originalHTML;
},
/////////////////////////////////////////////////////////////////////////////
// Private methods the implementation MUST NOT use or override
/////////////////////////////////////////////////////////////////////////////
/**
* Processes this tag.
*/
process: function() {
if (!('_originalHTML' in this)) {
this._originalHTML = FB.String.trim(this.dom.innerHTML);
}
if (!this.setupAndValidate()) {
// failure to validate means we're done rendering what we can
this.fire('render');
return;
}
var
size = this._getAttributeFromList('size', 'medium', this._allowedSizes),
className = '',
markup = '';
if (size == 'icon') {
className = 'fb_button_simple';
} else {
var rtl_suffix = FB._localeIsRtl ? '_rtl' : '';
markup = this.getButtonMarkup();
className = 'fb_button' + rtl_suffix + ' fb_button_' + size + rtl_suffix;
}
if (markup !== '') {
this.dom.innerHTML = (
'<a class="' + className + '">' +
'<span class="fb_button_text">' + markup + '</span>' +
'</a>'
);
// the firstChild is the anchor tag we just setup above
this.dom.firstChild.onclick = FB.bind(this.onClick, this);
}
this.fire('render');
}
});
/**
* 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.xfbml.addprofiletab
* @layer xfbml
* @requires fb.type
* fb.intl
* fb.ui
* fb.xfbml.buttonelement
* fb.helper
*/
/**
* Implementation for fb:add-profile-tab tag.
*
* @class FB.XFBML.AddProfileTab
* @extends FB.XFBML.ButtonElement
* @private
*/
FB.subclass('XFBML.AddProfileTab', 'XFBML.ButtonElement', null, {
/**
* Should return the button markup. The default behaviour is to return the
* original innerHTML of the element.
*
* @return {String} the HTML markup for the button
*/
getButtonMarkup: function() {
return FB.Intl.tx._("Add Profile Tab on Facebook");
},
/**
* The ButtonElement base class will invoke this when the button is clicked.
*/
onClick: function() {
FB.ui({ method: 'profile.addtab' }, this.bind(function(result) {
if (result.tab_added) {
FB.Helper.invokeHandler(this.getAttribute('on-add'), this);
}
}));
}
});
/**
* 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.xfbml.facepile
* @layer xfbml
* @requires fb.type fb.auth
*/
/**
* Implementation for fb:facepile tag.
*
* @class FB.XFBML.Facepile
* @extends FB.XFBML.Facepile
* @private
*/
FB.subclass('XFBML.Facepile', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
_extraParams: {},
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
href: this.getAttribute('href'),
channel: this.getChannelUrl(),
colorscheme: this.getAttribute('colorscheme', 'light'),
max_rows: this.getAttribute('max-rows'),
action: this.getAttribute('action', 'like'),
tense: this.getAttribute('tense', 'past'),
width: this._getPxAttribute('width', 200),
ref: this.getAttribute('ref'),
size: this.getAttribute('size', 'small'),
extended_social_context:
this.getAttribute('extended_social_context', false),
// inner html will become the login button text,
// if specified
login_text: this.dom.innerHTML
};
// clear out the inner html that we'll be using
// for the login button text
this.clear();
for (var key in this._extraParams) {
this._attr[key] = this._extraParams[key];
}
return true;
},
/**
* Sets extra parameters that will be passed to the widget's url.
*/
setExtraParams: function(val) {
this._extraParams = val;
},
/**
* Setup event handlers.
*/
oneTimeSetup: function() {
// this widget's internal state is tied to the "connected" status. it
// doesn't care about the difference between "unknown" and "notConnected".
var lastStatus = FB._userStatus;
FB.Event.subscribe('auth.statusChange', FB.bind(function(response) {
if (lastStatus == 'connected' || response.status == 'connected') {
this.process(true);
}
lastStatus = response.status;
}, this));
},
/**
* Get the initial size.
*
* By default, shows one row of 6 profiles
*
* @return {Object} the size
*/
getSize: function() {
// made height to 90 to accommodate large profile pic sizes
if (this._attr.size == 'large') {
return { width: this._attr.width, height: 90 };
}
return { width: this._attr.width, height: 70 };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'facepile', params: this._attr };
}
});
/**
* 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.xfbml.addtotimeline
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
*/
/**
* Implementation for fb:add-to-timeline tag.
*
* @class FB.XFBML.AddToTimeline
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.AddToTimeline', 'XFBML.Facepile', null, {
_visibleAfter: 'load',
/**
* Get the initial size.
*
* By default, 300x250
*
* @return {Object} the size
*/
getSize: function() {
return { width: 300, height: 250 };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'add_to_timeline', params: this._attr };
}
});
/**
* 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.xfbml.bookmark
* @layer xfbml
* @requires fb.type
* fb.intl
* fb.ui
* fb.xfbml.buttonelement
* fb.helper
*/
/**
* Implementation for fb:bookmark tag.
*
* @class FB.XFBML.Bookmark
* @extends FB.XFBML.ButtonElement
* @private
*/
FB.subclass('XFBML.Bookmark', 'XFBML.ButtonElement', null, {
/**
* Should return the button markup. The default behaviour is to return the
* original innerHTML of the element.
*
* @return {String} the HTML markup for the button
*/
getButtonMarkup: function() {
return FB.Intl.tx._("Bookmark on Facebook");
},
/**
* The ButtonElement base class will invoke this when the button is clicked.
*/
onClick: function() {
FB.ui({ method: 'bookmark.add' }, this.bind(function(result) {
if (result.bookmarked) {
FB.Helper.invokeHandler(this.getAttribute('on-add'), this);
}
}));
}
});
/**
* 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.xfbml.comments
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget fb.auth fb.ua
*/
/**
* Implementation for fb:comments tag.
*
* @class FB.XFBML.Comments
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Comments', 'XFBML.IframeWidget', null, {
_visibleAfter: 'immediate',
/**
* Refresh the iframe on auth.statusChange events.
*/
_refreshOnAuthChange: true,
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
// query parameters to the comments iframe
var attr = {
channel_url : this.getChannelUrl(),
colorscheme : this.getAttribute('colorscheme'),
numposts : this.getAttribute('num-posts', 10),
width : this._getPxAttribute('width', 550),
href : this.getAttribute('href'),
permalink : this.getAttribute('permalink'),
publish_feed : this.getAttribute('publish_feed'),
mobile : this._getBoolAttribute('mobile')
};
if (FB.initSitevars.enableMobileComments &&
FB.UA.mobile() &&
attr.mobile !== false) {
attr.mobile = true;
}
// legacy comments box params
if (!attr.href) {
attr.migrated = this.getAttribute('migrated');
attr.xid = this.getAttribute('xid');
attr.title = this.getAttribute('title', document.title);
attr.url = this.getAttribute('url', document.URL);
attr.quiet = this.getAttribute('quiet');
attr.reverse = this.getAttribute('reverse');
attr.simple = this.getAttribute('simple');
attr.css = this.getAttribute('css');
attr.notify = this.getAttribute('notify');
// default xid to current URL
if (!attr.xid) {
// We always want the URL minus the hash "#" also note the encoding here
// and down below when the url is built. This is intentional, so the
// string received by the server is url-encoded and thus valid.
var index = document.URL.indexOf('#');
if (index > 0) {
attr.xid = encodeURIComponent(document.URL.substring(0, index));
}
else {
attr.xid = encodeURIComponent(document.URL);
}
}
if (attr.migrated) {
attr.href =
'http://www.facebook.com/plugins/comments_v1.php?' +
'app_id=' + FB._apiKey +
'&xid=' + encodeURIComponent(attr.xid) +
'&url=' + encodeURIComponent(attr.url);
}
} else {
// allows deep linking of comments by surfacing linked comments
var fb_comment_id = this.getAttribute('fb_comment_id');
if (!fb_comment_id) {
fb_comment_id =
FB.QS.decode(
document.URL.substring(
document.URL.indexOf('?') + 1)).fb_comment_id;
if (fb_comment_id && fb_comment_id.indexOf('#') > 0) {
// strip out the hash if we managed to pick it up
fb_comment_id =
fb_comment_id.substring(0,
fb_comment_id.indexOf('#'));
}
}
if (fb_comment_id) {
attr.fb_comment_id = fb_comment_id;
this.subscribe('render',
FB.bind(function() {
window.location.hash = this.getIframeNode().id;
}, this));
}
}
this._attr = attr;
return true;
},
/**
* Setup event handlers.
*/
oneTimeSetup: function() {
this.subscribe('xd.addComment',
FB.bind(this._handleCommentMsg, this));
this.subscribe('xd.commentCreated',
FB.bind(this._handleCommentCreatedMsg, this));
this.subscribe('xd.commentRemoved',
FB.bind(this._handleCommentRemovedMsg, this));
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
if (this._attr.mobile) {
return { width: '100%', height: 160 };
}
return { width: this._attr.width, height: 160 };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'comments', params: this._attr };
},
/**
* Returns the default domain that should be used for the
* comments plugin being served from the web tier. Because
* of the complexities involved in serving up the comments
* plugin for logged out HTTPS users, and because a near-majority
* of comments plugins are rendered for logged out users, we
* just always load the comments plugin over https.
*/
getDefaultWebDomain: function() {
if (this._attr.mobile) {
return 'https_m';
} else {
return 'https_www';
}
},
/**
* Invoked by the iframe when a comment is added. Note, this feature needs to
* be enabled by specifying the notify=true attribute on the tag. This is in
* order to improve performance by only requiring this overhead when a
* developer explicitly said they want it.
*
* @param message {Object} the message received via XD
*/
_handleCommentMsg: function(message) {
//TODO (naitik) what should we be giving the developers here? is there a
// comment_id they can get?
if (!this.isValid()) {
return;
}
FB.Event.fire('comments.add', {
post: message.post,
user: message.user,
widget: this
});
},
_handleCommentCreatedMsg: function(message) {
if (!this.isValid()) {
return;
}
var eventArgs = {
href: message.href,
commentID: message.commentID,
parentCommentID: message.parentCommentID
};
FB.Event.fire('comment.create', eventArgs);
},
_handleCommentRemovedMsg: function(message) {
if (!this.isValid()) {
return;
}
var eventArgs = {
href: message.href,
commentID: message.commentID
};
FB.Event.fire('comment.remove', eventArgs);
}
});
/**
* @provides fb.xfbml.commentscount
* @requires fb.data
* fb.dom
* fb.helper
* fb.intl
* fb.string
* fb.type
* fb.ui
* fb.xfbml
* fb.xfbml.element
* @css fb.css.sharebutton
* @layer xfbml
*/
/**
* 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.
*/
/**
* Implementation for fb:comments-count tag.
* @class FB.XFBML.CommentsCount
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.CommentsCount', 'XFBML.Element', null, {
/**
* Processes this tag.
*/
process: function() {
this._href = this.getAttribute('href', window.location.href);
this._count = FB.Data._selectByIndex(
['commentsbox_count'],
'link_stat',
'url',
this._href
);
FB.Dom.addCss(this.dom, 'fb_comments_count_zero');
this._count.wait(
FB.bind(
function() {
var c = this._count.value[0].commentsbox_count;
this.dom.innerHTML = FB.String.format(
'<span class="fb_comments_count">{0}</span>',
c);
if (c > 0) {
FB.Dom.removeCss(this.dom, 'fb_comments_count_zero');
}
this.fire('render');
},
this)
);
}
});
/**
* 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.anim
* @layer basic
* @requires fb.prelude fb.array fb.dom
*/
/**
* This provides helper methods related to basic animation.
*
* @class FB.Anim
* @static
* @private
*/
FB.provide('Anim', {
/**
* Animate Transformable Element
*
* Note: only pixel, point, %, and opactity values are animate-able
*
* @param dom {DOMElement} the element to be animated
* @param props {Object} an object with the properties of the animation
* destination
* @param duration {Number} the number of milliseconds over which the
* animation should happen.
* @param callback {Function} the callback function to call after the
* animation is complete
*/
ate: function(dom, props, duration, callback) {
duration = !isNaN(parseFloat(duration)) && duration >= 0 ? duration : 750;
var
frame_speed = 40,
from = {},
to = {},
begin = null,
s = dom.style,
timer = setInterval(FB.bind(function() {
if (!begin) { begin = new Date().getTime(); }
// percent done
var pd = 1;
if (duration != 0) {
pd = Math.min((new Date().getTime() - begin) / duration, 1);
}
FB.Array.forEach(props, FB.bind(function(value, prop) {
if (!from[prop]) { // parse from CSS
var style = FB.Dom.getStyle(dom, prop);
// check for can't animate this, bad prop for this browser
if (style === false) { return; }
from[prop] = this._parseCSS(style+''); // force string type
}
if (!to[prop]) { // parse to CSS
to[prop] = this._parseCSS(value.toString());
}
var next = ''; // the next value to set
FB.Array.forEach(from[prop], function(pair, i) {
/* check for user override not animating this part via special
* symbol, "?". This is best used for animating properties with
* multiple parts, such as backgroundPosition, where you only want
* to animate one part and not the other.
*
* e.g.
* backgroundPosition: '8px 10px' => moves x and y to 8, 10
* backgroundPosition: '? 4px' => moves y to 4 and leaves x alone
* backgroundPosition: '7px ?' => moves x to 7 and leaves y alone
*/
if (isNaN(to[prop][i].numPart) && to[prop][i].textPart == '?') {
next = pair.numPart + pair.textPart;
/* check for a non animate-able part
* this includes colors (for now), positions, anything with out a #,
* etc.
*/
} else if (isNaN(pair.numPart)) {
next = pair.textPart;
// yay it's animate-able!
} else {
next +=
(pair.numPart + // orig value
Math.ceil((to[prop][i].numPart - pair.numPart) *
Math.sin(Math.PI/2 * pd))) +
to[prop][i].textPart + ' '; // text part and trailing space
}
});
// update with new value
FB.Dom.setStyle(dom, prop, next);
}, this));
if (pd == 1) { // are we done? clear the timer, call the callback
clearInterval(timer);
if (callback) { callback(dom); }
}
}, this), frame_speed);
},
/*
* Parses a CSS statment into it's parts
*
* e.g. "1px solid black" =>
* [[numPart: 1, textPart: 'px'],
* [numPart: NaN, textPart: 'solid'],
* [numPart: NaN, textPart: 'black']]
* or
* "5px 0% 2em none" =>
* [[numPart: 5, textPart: 'px'],
* [numPart: 0, textPart: '%'],
* [numPart: 2, textPart: 'em'],
* [numPart: NaN, textPart: 'none']]
*/
_parseCSS: function(css) {
var ret = [];
FB.Array.forEach(css.split(' '), function(peice) {
var num = parseInt(peice, 10);
ret.push({numPart: num, textPart: peice.replace(num,'')});
});
return ret;
}
});
/**
* 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;
}
});
/**
* @provides fb.xfbml.connectbar
* @requires fb.anim
* fb.auth
* fb.data
* fb.dom
* fb.event
* fb.helper
* fb.insights
* fb.intl
* fb.string
* fb.type
* fb.ua
* fb.xfbml
* fb.xfbml.element
* @css fb.css.connectbarwidget
* @layer xfbml
*/
/**
* 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.
*/
/**
* @class FB.XFBML.ConnectBar
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.ConnectBar', 'XFBML.Element', null, {
_initialHeight: null,
_initTopMargin: 0,
_picFieldName: 'pic_square',
_page: null, // the external site's content parent node
_displayed: false, // is the bar currently displayed
_notDisplayed: false, // is the bar currently not displayed
_container: null,
_animationSpeed: 0, // default to no (zero time, instant) animation
/**
* Processes this tag.
*/
process: function() {
// Wait for status to be known
FB.getLoginStatus(this.bind(function(resp) {
FB.Event.monitor('auth.statusChange', this.bind(function() {
// Is Element still in DOM tree? are we connected?
if (this.isValid() && FB._userStatus == 'connected') {
this._uid = FB.Helper.getLoggedInUser();
FB.api({ // check if marked seen / current seen count
method: 'Connect.shouldShowConnectBar'
}, this.bind(function(showBar) {
if (showBar != 2) {
this._animationSpeed = (showBar == 0) ? 750 : 0;
this._showBar();
} else {
this._noRender();
}
}));
} else {
this._noRender();
}
return false; // continue monitoring
}));
}));
},
/**
* load the data for the bar and render it firing all the events in the
* process
*/
_showBar: function() {
var q1 = FB.Data._selectByIndex(['first_name', 'profile_url',
this._picFieldName],
'user', 'uid', this._uid);
var q2 = FB.Data._selectByIndex(['display_name'], 'application',
'api_key', FB._apiKey);
FB.Data.waitOn([q1, q2], FB.bind(function(data) {
data[0][0].site_name = data[1][0].display_name;
if (!this._displayed) {
this._displayed = true;
this._notDisplayed = false;
this._renderConnectBar(data[0][0]);
this.fire('render');
FB.Insights.impression({
lid: 104,
name: 'widget_load'
});
this.fire('connectbar.ondisplay');
FB.Event.fire('connectbar.ondisplay', this);
FB.Helper.invokeHandler(this.getAttribute('on-display'), this);
}
}, this));
},
/**
* If the bar is rendered, hide it and fire the no render events
*/
_noRender: function() {
if (this._displayed) {
this._displayed = false;
this._closeConnectBar();
}
if (!this._notDisplayed) {
this._notDisplayed = true;
this.fire('render');
this.fire('connectbar.onnotdisplay');
FB.Event.fire('connectbar.onnotdisplay', this);
FB.Helper.invokeHandler(this.getAttribute('on-not-display'), this);
}
},
/**
* Given this name, site name, and profile pic url render the connect bar
*/
_renderConnectBar: function(info) {
var bar = document.createElement('div'),
container = document.createElement('div');
// TODO(alpjor) add rtl support
bar.className = 'fb_connect_bar';
container.className = 'fb_reset fb_connect_bar_container';
container.appendChild(bar);
document.body.appendChild(container);
this._container = container;
this._initialHeight = Math.round(
parseFloat(FB.Dom.getStyle(container, 'height')) +
parseFloat(FB.Dom.getStyle(container, 'borderBottomWidth')));
bar.innerHTML = FB.String.format(
'<div class="fb_buttons">' +
'<a href="#" class="fb_bar_close">' +
'<img src="{1}" alt="{2}" title="{2}"/>' +
'</a>' +
'</div>' +
'<a href="{7}" class="fb_profile" target="_blank">' +
'<img src="{3}" alt="{4}" title="{4}"/>' +
'</a>' +
'{5}' +
' <span>' +
'<a href="{8}" class="fb_learn_more" target="_blank">{6}</a> &ndash; ' +
'<a href="#" class="fb_no_thanks">{0}</a>' +
'</span>',
FB.Intl.tx._("No Thanks"),
FB.getDomain('cdn') + FB.XFBML.ConnectBar.imgs.buttonUrl,
FB.Intl.tx._("Close"),
info[this._picFieldName] || FB.getDomain('cdn') +
FB.XFBML.ConnectBar.imgs.missingProfileUrl,
FB.String.escapeHTML(info.first_name),
FB.Intl.tx._("Hi {firstName}. \u003Cstrong>{siteName}\u003C\/strong> is using Facebook to personalize your experience.", {
firstName: FB.String.escapeHTML(info.first_name),
siteName: FB.String.escapeHTML(info.site_name)
}),
FB.Intl.tx._("Learn More"),
info.profile_url,
FB.getDomain('www') + 'sitetour/connect.php'
);
var _this = this;
FB.Array.forEach(bar.getElementsByTagName('a'), function(el) {
el.onclick = FB.bind(_this._clickHandler, _this);
});
this._page = document.body;
var top_margin = 0;
if (this._page.parentNode) {
top_margin = Math.round(
(parseFloat(FB.Dom.getStyle(this._page.parentNode, 'height')) -
parseFloat(FB.Dom.getStyle(this._page, 'height'))) / 2);
} else {
top_margin = parseInt(FB.Dom.getStyle(this._page, 'marginTop'), 10);
}
top_margin = isNaN(top_margin) ? 0 : top_margin;
this._initTopMargin = top_margin;
if (!window.XMLHttpRequest) { // ie6
container.className += " fb_connect_bar_container_ie6";
} else {
container.style.top = (-1*this._initialHeight) + 'px';
FB.Anim.ate(container, { top: '0px' }, this._animationSpeed);
}
var move = { marginTop: this._initTopMargin + this._initialHeight + 'px' }
if (FB.UA.ie()) { // for ie
move.backgroundPositionY = this._initialHeight + 'px'
} else { // for others
move.backgroundPosition = '? ' + this._initialHeight + 'px'
}
FB.Anim.ate(this._page, move, this._animationSpeed);
},
/**
* Handle the anchor clicks from the connect bar
*
*/
_clickHandler : function(e) {
e = e || window.event;
var el = e.target || e.srcElement;
while (el.nodeName != 'A') { el = el.parentNode; }
switch (el.className) {
case 'fb_bar_close':
FB.api({ // mark seen
method: 'Connect.connectBarMarkAcknowledged'
});
FB.Insights.impression({
lid: 104,
name: 'widget_user_closed'
});
this._closeConnectBar();
break;
case 'fb_learn_more':
case 'fb_profile':
window.open(el.href);
break;
case 'fb_no_thanks':
this._closeConnectBar();
FB.api({ // mark seen
method: 'Connect.connectBarMarkAcknowledged'
});
FB.Insights.impression({
lid: 104,
name: 'widget_user_no_thanks'
});
FB.api({ method: 'auth.revokeAuthorization', block: true },
this.bind(function() {
this.fire('connectbar.ondeauth');
FB.Event.fire('connectbar.ondeauth', this);
FB.Helper.invokeHandler(this.getAttribute('on-deauth'), this);
if (this._getBoolAttribute('auto-refresh', true)) {
window.location.reload();
}
}));
break;
}
return false;
},
_closeConnectBar: function() {
this._notDisplayed = true;
var move = { marginTop: this._initTopMargin + 'px' }
if (FB.UA.ie()) { // for ie
move.backgroundPositionY = '0px'
} else { // for others
move.backgroundPosition = '? 0px'
}
var speed = (this._animationSpeed == 0) ? 0 : 300;
FB.Anim.ate(this._page, move, speed);
FB.Anim.ate(this._container, {
top: (-1 * this._initialHeight) + 'px'
}, speed, function(el) {
el.parentNode.removeChild(el);
});
this.fire('connectbar.onclose');
FB.Event.fire('connectbar.onclose', this);
FB.Helper.invokeHandler(this.getAttribute('on-close'), this);
}
});
FB.provide('XFBML.ConnectBar', {
imgs: {
buttonUrl: 'images/facebook-widgets/close_btn.png',
missingProfileUrl: 'pics/q_silhouette.gif'
}
});
/**
* 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.xfbml.fan
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget
*/
/**
* Implementation for fb:fan tag.
*
* @class FB.XFBML.Fan
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Fan', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
api_key : FB._apiKey,
connections : this.getAttribute('connections', '10'),
css : this.getAttribute('css'),
height : this._getPxAttribute('height'),
id : this.getAttribute('profile-id'),
logobar : this._getBoolAttribute('logo-bar'),
name : this.getAttribute('name'),
stream : this._getBoolAttribute('stream', true),
width : this._getPxAttribute('width', 300)
};
// "id" or "name" is required
if (!this._attr.id && !this._attr.name) {
FB.log('<fb:fan> requires one of the "id" or "name" attributes.');
return false;
}
var height = this._attr.height;
if (!height) {
if ((!this._attr.connections || this._attr.connections === '0') &&
!this._attr.stream) {
height = 65;
} else if (!this._attr.connections || this._attr.connections === '0') {
height = 375;
} else if (!this._attr.stream) {
height = 250;
} else {
height = 550;
}
}
// add space for logobar
if (this._attr.logobar) {
height += 25;
}
this._attr.height = height;
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'fan', params: this._attr };
}
});
/**
* 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.xfbml.friendpile
* @layer xfbml
* @requires fb.type fb.xfbml.facepile
*/
/**
* Implementation for fb:friendpile tag.
*
* @class FB.XFBML.Friendpile
* @extends FB.XFBML.Friendpile
* @private
*/
FB.subclass('XFBML.Friendpile', 'XFBML.Facepile', null, {});
/**
* @provides fb.xfbml.edgecommentwidget
* @requires fb.type
* fb.xfbml.iframewidget
* @css fb.css.edgecommentwidget
* @layer xfbml
*/
/**
* 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.
*/
/**
* Base implementation for Edge Comment Widgets.
*
* @class FB.XFBML.EdgeCommentWidget
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.EdgeCommentWidget', 'XFBML.IframeWidget',
function(opts) {
this._iframeWidth = opts.width + 1;
this._iframeHeight = opts.height;
this._attr = {
master_frame_name: opts.masterFrameName,
offsetX: opts.relativeWidthOffset - opts.paddingLeft
};
this.dom = opts.commentNode;
this.dom.style.top = opts.relativeHeightOffset + 'px';
this.dom.style.left = opts.relativeWidthOffset + 'px';
this.dom.style.zIndex = FB.XFBML.EdgeCommentWidget.NextZIndex++;
FB.Dom.addCss(this.dom, 'fb_edge_comment_widget');
}, {
/////////////////////////////////////////////////////////////////////////////
// Internal stuff.
/////////////////////////////////////////////////////////////////////////////
/**
* Make the iframe visible only when it has finished loading.
*/
_visibleAfter: 'load',
_showLoader: false,
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return {
width: this._iframeWidth,
height: this._iframeHeight
};
},
/**
* Get there URL bits for the iframe.
*
* @return {Object} the iframe URL bits.
*/
getUrlBits: function() {
return { name: 'comment_widget_shell', params: this._attr };
}
});
FB.provide('XFBML.EdgeCommentWidget', {
NextZIndex : 10000
});
/**
* 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.xfbml.edgewidget
* @layer xfbml
* @requires fb.type
* fb.dom
* fb.event
* fb.helper
* fb.xfbml.iframewidget
* fb.xfbml.edgecommentwidget
*/
/**
* Base implementation for Edge Widgets.
*
* @class FB.XFBML.EdgeWidget
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.EdgeWidget', 'XFBML.IframeWidget', null, {
/**
* Make the iframe visible only when it has finished loading.
*/
_visibleAfter: 'immediate',
_showLoader: false,
_rootPadding: null,
/**
* Do initial attribute processing.
*/
setupAndValidate : function() {
FB.Dom.addCss(this.dom, 'fb_edge_widget_with_comment');
this._attr = {
channel_url : this.getChannelUrl(),
debug : this._getBoolAttribute('debug'),
href : this.getAttribute('href', window.location.href),
is_permalink : this._getBoolAttribute('is-permalink'),
node_type : this.getAttribute('node-type', 'link'),
width : this._getWidgetWidth(),
font : this.getAttribute('font'),
layout : this._getLayout(),
colorscheme : this.getAttribute('color-scheme'),
action : this.getAttribute('action'),
ref : this.getAttribute('ref'),
show_faces : this._shouldShowFaces(),
no_resize : this._getBoolAttribute('no_resize'),
send : this._getBoolAttribute('send'),
url_map : this.getAttribute('url_map'),
extended_social_context :
this._getBoolAttribute('extended_social_context', false)
};
this._rootPadding = {
left: parseFloat(FB.Dom.getStyle(this.dom, 'paddingLeft')),
top: parseFloat(FB.Dom.getStyle(this.dom, 'paddingTop'))
};
return true;
},
oneTimeSetup : function() {
this.subscribe('xd.authPrompted',
FB.bind(this._onAuthPrompt, this));
this.subscribe('xd.edgeCreated',
FB.bind(this._onEdgeCreate, this));
this.subscribe('xd.edgeRemoved',
FB.bind(this._onEdgeRemove, this));
this.subscribe('xd.presentEdgeCommentDialog',
FB.bind(this._handleEdgeCommentDialogPresentation, this));
this.subscribe('xd.dismissEdgeCommentDialog',
FB.bind(this._handleEdgeCommentDialogDismissal, this));
this.subscribe('xd.hideEdgeCommentDialog',
FB.bind(this._handleEdgeCommentDialogHide, this));
this.subscribe('xd.showEdgeCommentDialog',
FB.bind(this._handleEdgeCommentDialogShow, this));
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return {
width: this._getWidgetWidth(),
height: this._getWidgetHeight()
};
},
/**
* Returns the height of the widget iframe, taking into
* account the chosen layout, a user-supplied height, and
* the min and max values we'll allow. As it turns out, we
* don't see too much. (At the moment, we ignore the any
* user-defined height, but that might change.)
*
* This logic is replicated in html/plugins/like.php and
* lib/external_node/param_validation.php, and must be replicated
* because it helps size the client's iframe.
*
* @return {String} the CSS-legitimate width in pixels, as
* with '460px'.
*/
_getWidgetHeight : function() {
var layout = this._getLayout();
var should_show_faces = this._shouldShowFaces() ? 'show' : 'hide';
var send = this._getBoolAttribute('send');
var box_count = 65 + (send ? 25 : 0);
var layoutToDefaultHeightMap =
{ 'standard' : {'show': 80, 'hide': 35},
'box_count' : {'show': box_count, 'hide': box_count},
'button_count' : {'show': 21, 'hide': 21},
'simple' : {'show': 20, 'hide': 20}};
return layoutToDefaultHeightMap[layout][should_show_faces];
},
/**
* Returns the width of the widget iframe, taking into
* account the chosen layout, the user supplied width, and
* the min and max values we'll allow. There is much more
* flexibility in how wide the widget is, so a user-supplied
* width just needs to fall within a certain range.
*
* This logic is replicated in html/plugins/like.php and
* lib/external_node/param_validation.php, and must be replicated
* because it helps size the client's iframe.
*
* @return {String} the CSS-legitimate width in pixels, as
* with '460px'.
*/
_getWidgetWidth : function() {
var layout = this._getLayout();
var send = this._getBoolAttribute('send');
var should_show_faces = this._shouldShowFaces() ? 'show' : 'hide';
var recommend = (this.getAttribute('action') === 'recommend');
var standard_min_width =
(recommend ? 265 : 225) + (send ? 60 : 0);
var button_count_default_width =
(recommend ? 130 : 90) + (send ? 60 : 0);
var box_count_default_width =
this.getAttribute('action') === 'recommend' ? 100 : 55;
var simple_default_width =
this.getAttribute('action') === 'recommend' ? 90 : 50;
var layoutToDefaultWidthMap =
{ 'standard': {'show': 450,
'hide': 450},
'box_count': {'show': box_count_default_width,
'hide': box_count_default_width},
'button_count': {'show': button_count_default_width,
'hide': button_count_default_width},
'simple': {'show': simple_default_width,
'hide': simple_default_width}};
var defaultWidth = layoutToDefaultWidthMap[layout][should_show_faces];
var width = this._getPxAttribute('width', defaultWidth);
var allowedWidths =
{ 'standard' : {'min' : standard_min_width, 'max' : 900},
'box_count' : {'min' : box_count_default_width,
'max' : 900},
'button_count' : {'min' : button_count_default_width,
'max' : 900},
'simple' : {'min' : 49,
'max' : 900}};
if (width < allowedWidths[layout].min) {
width = allowedWidths[layout].min;
} else if (width > allowedWidths[layout].max) {
width = allowedWidths[layout].max;
}
return width;
},
/**
* Returns the layout provided by the user, which can be
* any one of 'standard', 'box', or 'bar'. If the user
* omits a layout, or if they layout they specify is invalid,
* then we just go with 'standard'.
*
* This logic is replicated in html/plugins/like.php and
* lib/external_node/param_validation.php, and must be replicated
* because it helps size the client's iframe.
*
* @return {String} the layout of the Connect Widget.
*/
_getLayout : function() {
return this._getAttributeFromList(
'layout',
'standard',
['standard', 'button_count', 'box_count', 'simple']);
},
/**
* Returns true if and only if we should be showing faces in the
* widget, and false otherwise.
*
* This logic is replicated in html/plugins/like.php and
* lib/external_node/param_validation.php, and must be replicated
* because it helps size the client's iframe.
*
* @return {String} described above.
*/
_shouldShowFaces : function() {
return this._getLayout() === 'standard' &&
this._getBoolAttribute('show-faces', true);
},
/**
* Handles the event fired when the user actually connects to
* something. The idea is to tell the host to drop in
* another iframe widget--an FB.XFBML.EdgeCommentWidget--
* and sensibly position it so it partially overlays
* the mother widget.
*
* @param {Object} message a dictionary of information about the
* event.
* @return void
*/
_handleEdgeCommentDialogPresentation : function(message) {
if (!this.isValid()) {
return;
}
var comment_node = document.createElement('span');
this._commentSlave = this._createEdgeCommentWidget(message, comment_node);
this.dom.appendChild(comment_node);
this._commentSlave.process();
this._commentWidgetNode = comment_node;
},
/**
* Given a message from an xd comm event and the node where the edge comment
* widget is to live in the dom, create an instance of
* FB.XFBML.EdgeCommentWidget with the default parameters for an edge
* widget. The idea is to allow this method to be overridden so that
* other edge widgets can have different parameters and maybe even other
* (sub)types of FB.XFBML.EdgeCommentWidget (e.g. SendButtonFormWidget).
*
* @param {Object} message a dictionary of information about the xd comm event
* @param {Object} comment_node the dom node where the edgecommentwidget
* will live.
* @return FB.XFBML.EdgeCommentWidget
*/
_createEdgeCommentWidget : function(message, comment_node) {
var opts = {
commentNode : comment_node,
externalUrl : message.externalURL,
masterFrameName : message.masterFrameName,
layout : this._getLayout(),
relativeHeightOffset : this._getHeightOffset(message),
relativeWidthOffset : this._getWidthOffset(message),
anchorTargetX : parseFloat(message['query[anchorTargetX]']) +
this._rootPadding.left,
anchorTargetY : parseFloat(message['query[anchorTargetY]']) +
this._rootPadding.top,
width : parseFloat(message.width),
height : parseFloat(message.height),
paddingLeft : this._rootPadding.left
};
return new FB.XFBML.EdgeCommentWidget(opts);
},
/**
* Determines the relative height offset of the external comment
* widget relative to the top of the primary like widget.
* The numbers can potentially vary from layout to layout, because
* the comment widget anchors on varying elements (button itself
* in button_count and standard, the favicon in box_count, etc.)
*
* @param {Object} message the full message of information passed from
* like plugin to the comment widget.
* @return {Number} relative offset that should influence
* placement of the external comment widget vsv the primary
* comment widget.
*/
_getHeightOffset : function(message) {
return parseFloat(message['anchorGeometry[y]']) +
parseFloat(message['anchorPosition[y]']) +
this._rootPadding.top;
},
/**
* Determines the relative offset of the external comment
* widget relative to the left (or right for RTL locales)
* of the primary like widget.
*
* @param {Object} message the full message of information passed from
* like plugin to the comment widget.
* @return {Number} relative offset than should influence
* placement of the external comment widget vsv the primary
* comment widget.
*/
_getWidthOffset : function(message) {
var off = parseFloat(message['anchorPosition[x]']) + this._rootPadding.left;
var plugin_left = FB.Dom.getPosition(this.dom).x;
var plugin_width = this.dom.offsetWidth;
var screen_width = FB.Dom.getViewportInfo().width;
var comment_width = parseFloat(message.width);
var flipit = false;
if (FB._localeIsRtl) {
flipit = comment_width < plugin_left;
} else if ((plugin_left + comment_width) > screen_width) {
flipit = true;
}
if (flipit) {
off += parseFloat(message['anchorGeometry[x]']) - comment_width;
}
return off;
},
/**
* Returns an object of options that is used in several types of edge comment
* widget
*
* @param {Object} message a dictionary of information about the xd comm event
* @param {Object} comment_node the dom node where the edgecommentwidget
* will live
* @return {Object} options
*/
_getCommonEdgeCommentWidgetOpts : function(message,
comment_node) {
return {
colorscheme : this._attr.colorscheme,
commentNode : comment_node,
controllerID : message.controllerID,
nodeImageURL : message.nodeImageURL,
nodeRef : this._attr.ref,
nodeTitle : message.nodeTitle,
nodeURL : message.nodeURL,
nodeSummary : message.nodeSummary,
width : parseFloat(message.width),
height : parseFloat(message.height),
relativeHeightOffset : this._getHeightOffset(message),
relativeWidthOffset : this._getWidthOffset(message),
error : message.error,
siderender : message.siderender,
extended_social_context : message.extended_social_context,
anchorTargetX : parseFloat(message['query[anchorTargetX]']) +
this._rootPadding.left,
anchorTargetY : parseFloat(message['query[anchorTargetY]']) +
this._rootPadding.top
};
},
/**
* Handles the XD event instructing the host to
* remove the comment widget iframe. The DOM node
* for this widget is currently carrying just one child
* node, which is the span representing the iframe.
* We just need to return that one child in order for the
* comment widget to disappear.
*
* @param {Object} message a dictionary of information about
* the event.
* @return void
*/
_handleEdgeCommentDialogDismissal : function(message) {
if (this._commentWidgetNode) {
this.dom.removeChild(this._commentWidgetNode);
delete this._commentWidgetNode;
}
},
/**
* Handles the XD event instructing the hose to hide the comment
* widget iframe.
*/
_handleEdgeCommentDialogHide: function() {
if (this._commentWidgetNode) {
this._commentWidgetNode.style.display="none";
}
},
/**
* Handles the XD event instructing the hose to show the comment
* widget iframe.
*/
_handleEdgeCommentDialogShow: function() {
if (this._commentWidgetNode) {
this._commentWidgetNode.style.display="block";
}
},
/**
* Helper method that fires a specified event and also invokes the
* given inline handler.
*/
_fireEventAndInvokeHandler: function(eventName, eventAttribute) {
FB.Helper.fireEvent(eventName, this);
FB.Helper.invokeHandler(
this.getAttribute(eventAttribute), this, [this._attr.href]); // inline
},
/**
* Invoked when the user likes/recommends/whatever the thing to create an
* edge.
*/
_onEdgeCreate: function() {
this._fireEventAndInvokeHandler('edge.create', 'on-create');
},
/**
* Invoked when the user removes a like/recommendation/etc association with
* something.
*/
_onEdgeRemove: function() {
this._fireEventAndInvokeHandler('edge.remove', 'on-remove');
},
/**
* Invoked when the user is prompted to opt-in/log-in due to clicking
* on an edge widget in an un-authed state.
*/
_onAuthPrompt: function() {
this._fireEventAndInvokeHandler('auth.prompt', 'on-prompt');
}
});
/**
* @provides fb.xfbml.sendbuttonformwidget
* @requires fb.type
* fb.xfbml.edgecommentwidget
* @css fb.css.sendbuttonformwidget
* @layer xfbml
*/
/**
* 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.
*/
/**
* Implementation for the send button form widget,
* which is an edge comment widget.
*
* @class FB.XFBML.SendButtonFormWidget
* @extends FB.XFBML.EdgeCommentWidget
* @private
*/
FB.subclass('XFBML.SendButtonFormWidget', 'XFBML.EdgeCommentWidget',
function(opts) {
this._base(opts);
FB.Dom.addCss(this.dom, 'fb_send_button_form_widget');
FB.Dom.addCss(this.dom, opts.colorscheme);
FB.Dom.addCss(this.dom,
(typeof opts.siderender != 'undefined' && opts.siderender) ?
'siderender' : '');
// The url title, and image URL of the node
this._attr.nodeImageURL = opts.nodeImageURL;
this._attr.nodeRef = opts.nodeRef;
this._attr.nodeTitle = opts.nodeTitle;
this._attr.nodeURL = opts.nodeURL;
this._attr.nodeSummary = opts.nodeSummary;
this._attr.offsetX = opts.relativeWidthOffset;
this._attr.offsetY = opts.relativeHeightOffset;
this._attr.anchorTargetX = opts.anchorTargetX;
this._attr.anchorTargetY = opts.anchorTargetY;
// Main channel
this._attr.channel = this.getChannelUrl();
// We use the controller ID of the SendButton js controller
// in order for the form controller to inform (via cross-frame Arbiter)
// the SendButton js controller of events in the form (e.g. message sent).
this._attr.controllerID = opts.controllerID;
// Light or dark color scheme.
this._attr.colorscheme = opts.colorscheme;
// If there was any error retrieving the node
this._attr.error = opts.error;
// whether the flyout to appear to come out from the side or from above
this._attr.siderender = opts.siderender;
// Determine if we should show extended social context on the widget
this._attr.extended_social_context = opts.extended_social_context;
}, {
// Since this comment widget is rendered on its own
// instead of having html injected into it,
// there will be a very small delay. So in meantime, let's show a loader
_showLoader: true,
getUrlBits: function() {
return { name: 'send_button_form_shell', params: this._attr };
},
oneTimeSetup: function() {
this.subscribe('xd.messageSent',
FB.bind(this._onMessageSent, this));
},
_onMessageSent: function() {
FB.Event.fire('message.send', this._attr.nodeURL, this);
}
});
/**
* 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.xfbml.send
* @layer xfbml
* @requires fb.type
* fb.xfbml.edgewidget
* fb.xfbml.sendbuttonformwidget
*/
/**
* Implementation for the fb:send tag.
*
* @class FB.XFBML.Send
* @extends FB.XFBML.EdgeWidget
* @private
*/
FB.subclass('XFBML.Send', 'XFBML.EdgeWidget', null, {
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
FB.Dom.addCss(this.dom, 'fb_edge_widget_with_comment');
this._attr = {
channel : this.getChannelUrl(),
api_key : FB._apiKey,
font : this.getAttribute('font'),
colorscheme : this.getAttribute('colorscheme', 'light'),
href : this.getAttribute('href', window.location.href),
ref : this.getAttribute('ref'),
extended_social_context :
this.getAttribute('extended_social_context', false)
};
this._rootPadding = {
left: parseFloat(FB.Dom.getStyle(this.dom, 'paddingLeft')),
top: parseFloat(FB.Dom.getStyle(this.dom, 'paddingTop'))
};
return true;
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'send', params: this._attr };
},
/**
* Given a message from an xd comm event and the node where the edge comment
* widget is to live in the dom, create an instance of
* FB.XFBML.SendButtonFormWidget to be displayed right below the send button.
*
* @param {Object} message a dictionary of information about the xd comm event
* @param {Object} comment_node the dom node where the edgecommentwidget
* will live.
* @return FB.XFBML.EdgeCommentWidget
*/
_createEdgeCommentWidget: function(message, comment_node) {
var opts = this._getCommonEdgeCommentWidgetOpts(message, comment_node);
return new FB.XFBML.SendButtonFormWidget(opts);
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return {
width : FB.XFBML.Send.Dimensions.width,
height : FB.XFBML.Send.Dimensions.height
};
}
});
FB.provide('XFBML.Send', {
Dimensions: {
width: 80,
height: 25
}
});
/**
* 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.xfbml.like
* @layer xfbml
* @requires fb.type
* fb.xfbml.edgewidget
* fb.xfbml.send
* fb.intl
*/
/**
* Implementation for fb:like tag.
*
* @class FB.XFBML.Like
* @extends FB.XFBML.EdgeWidget
* @private
*/
FB.subclass('XFBML.Like', 'XFBML.EdgeWidget', null, {
/**
* Like Plugin isn't actually widget-pipe enabled yet, though
* in principle it can be by just switching this false to
* true.
*/
_widgetPipeEnabled: true,
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'like', params: this._attr };
},
/**
* Given a message from an xd comm event and the node where the edge comment
* widget is to live in the dom, create an instance of
* FB.XFBML.EdgeCommentWidget to be displayed right below the button.
*
* @param {Object} message a dictionary of information about the xd comm event
* @param {Object} comment_node the dom node where the edgecommentwidget
* will live.
* @return FB.XFBML.EdgeCommentWidget
*/
_createEdgeCommentWidget: function(message, comment_node) {
// the like widget is also responsible for the send button form if the user
// decides to put a send button with the like widget (e.g. <fb:like
// send="true"></fb:like>)
if ('send' in this._attr && 'widget_type' in message &&
message.widget_type == 'send') {
var opts = this._getCommonEdgeCommentWidgetOpts(message,
comment_node);
return new FB.XFBML.SendButtonFormWidget(opts);
} else {
return this._callBase("_createEdgeCommentWidget",
message,
comment_node);
}
},
/**
* Get the title attribute for the like plugin's iframe.
*
* @return {String} the title of the like plugin's iframe.
*/
getIframeTitle: function() {
return 'Like this content on Facebook.';
}
});
/**
* 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.xfbml.likebox
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget
*/
/**
* Implementation for fb:like-box tag.
*
* @class FB.XFBML.LikeBox
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.LikeBox', 'XFBML.EdgeWidget', null, {
_visibleAfter: 'load',
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
channel : this.getChannelUrl(),
api_key : FB._apiKey,
connections : this.getAttribute('connections'),
css : this.getAttribute('css'),
height : this.getAttribute('height'),
id : this.getAttribute('profile-id'),
header : this._getBoolAttribute('header', true),
name : this.getAttribute('name'),
show_faces : this._getBoolAttribute('show-faces', true),
stream : this._getBoolAttribute('stream', true),
width : this._getPxAttribute('width', 300),
href : this.getAttribute('href'),
colorscheme : this.getAttribute('colorscheme', 'light'),
border_color: this.getAttribute('border_color')
};
if (this._getBoolAttribute('force_wall', false)) {
this._attr.force_wall = true;
}
// also allow connections attr, if specified, to override
// show_faces
if (this._attr.connections === '0') {
this._attr.show_faces = false;
} else if (this._attr.connections) {
this._attr.show_faces = true;
}
// "id" or "name" or "href" (for Open Graph) is required
if (!this._attr.id && !this._attr.name && !this._attr.href) {
FB.log('<fb:like-box> requires one of the "id" or "name" attributes.');
return false;
}
var height = this._attr.height;
if (!height) {
if (!this._attr.show_faces &&
!this._attr.stream) {
height = 62;
} else {
height = 95;
if (this._attr.show_faces) {
height += 163;
}
if (this._attr.stream) {
height += 300;
}
// add space for header
if (this._attr.header &&
this._attr.header !== '0') {
height += 32;
}
}
}
this._attr.height = height;
// listen for the XD 'likeboxLiked' and 'likeboxUnliked' events
this.subscribe('xd.likeboxLiked', FB.bind(this._onLiked, this));
this.subscribe('xd.likeboxUnliked', FB.bind(this._onUnliked, this));
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'likebox', params: this._attr };
},
/**
* Invoked when the user Likes the page via the Like button
* in the likebox.
*/
_onLiked: function() {
FB.Helper.fireEvent('edge.create', this);
},
/**
* Invoked when the user Unlikes the page via the Unlike button
* in the likebox.
*/
_onUnliked: function() {
FB.Helper.fireEvent('edge.remove', this);
}
});
/**
* 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.xfbml.livestream
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget
*/
/**
* Implementation for fb:live-stream tag.
*
* @class FB.XFBML.LiveStream
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.LiveStream', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
app_id : this.getAttribute('event-app-id'),
height : this._getPxAttribute('height', 500),
hideFriendsTab : this.getAttribute('hide-friends-tab'),
redesigned : this._getBoolAttribute('redesigned-stream'),
width : this._getPxAttribute('width', 400),
xid : this.getAttribute('xid', 'default'),
always_post_to_friends : this._getBoolAttribute('always-post-to-friends'),
via_url : this.getAttribute('via_url')
};
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
var name = this._attr.redesigned ? 'live_stream_box' : 'livefeed';
if (this._getBoolAttribute('modern', false)) {
name = 'live_stream';
}
return { name: name, params: this._attr };
}
});
/**
* 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.xfbml.login
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
*/
/**
* Implementation for fb:login tag.
*
* @class FB.XFBML.Login
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Login', 'XFBML.Facepile', null, {
_visibleAfter: 'load',
/**
* Get the initial size.
*
* By default, shows one row of 6 profiles
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: 94 };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'login', params: this._attr };
}
});
/**
* 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.xfbml.loginbutton
* @layer xfbml
* @requires fb.type
* fb.intl
* fb.xfbml.buttonelement
* fb.helper
* fb.auth
*/
/**
* Implementation for fb:login-button tag.
*
* @class FB.XFBML.LoginButton
* @extends FB.XFBML.ButtonElement
* @private
*/
FB.subclass('XFBML.LoginButton', 'XFBML.ButtonElement', null, {
/**
* Do initial attribute processing.
*
* @return {Boolean} true to continue processing, false to halt it
*/
setupAndValidate: function() {
// This method sometimes makes a callback that will call process() which
// in-tern calls this method. Let's only setup once.
if (this._alreadySetup) {
return true;
}
this._alreadySetup = true;
this._attr = {
autologoutlink : this._getBoolAttribute('auto-logout-link'),
length : this._getAttributeFromList(
'length', // name
'short', // defaultValue
['long', 'short'] // allowed
),
onlogin : this.getAttribute('on-login'),
perms : this.getAttribute('perms'),
scope : this.getAttribute('scope'),
registration_url : this.getAttribute('registration-url'),
status : 'unknown'
};
// Change the displayed HTML whenever we know more about the auth status
if (this._attr.autologoutlink) {
FB.Event.subscribe('auth.statusChange', FB.bind(this.process, this));
}
if (this._attr.registration_url) {
// The act of putting a registration-url means that unTOSed users will
// be redirected to the registration widget instead of the TOS dialog.
FB.Event.subscribe('auth.statusChange',
this._saveStatus(this.process, /* on_click */ false));
// Change the displayed HTML immediately
FB.getLoginStatus(this._saveStatus(this.process, /* on_click */ false));
}
return true;
},
/**
* Should return the button markup. The default behaviour is to return the
* original innerHTML of the element.
*
* @return {String} the HTML markup for the button
*/
getButtonMarkup: function() {
var originalHTML = this.getOriginalHTML();
if (originalHTML) {
return originalHTML;
}
if (!this._attr.registration_url) {
// In the normal sense, it says "Logout" or "Login"
if (FB.getAccessToken() && this._attr.autologoutlink) {
return FB.Intl.tx._("Facebook Logout");
} else if (FB.getAccessToken()) {
// logged in already, don't render anything
return '';
} else {
return this._getLoginText();
}
} else {
// If there is a registration url it says "Logout", "Register" or "Login"
switch (this._attr.status) {
case 'unknown':
return this._getLoginText();
case 'notConnected':
case 'not_authorized':
return FB.Intl.tx._("Register");
case 'connected':
if (FB.getAccessToken() && this._attr.autologoutlink) {
return FB.Intl.tx._("Facebook Logout");
}
// registered already, don't render anything
return '';
default:
FB.log('Unknown status: ' + this._attr.status);
return FB.Intl.tx._("Log In");
}
}
},
/**
* Helper function to show "Login" or "Login with Facebook"
*/
_getLoginText: function() {
return this._attr.length == 'short'
? FB.Intl.tx._("Log In")
: FB.Intl.tx._("Log In with Facebook");
},
/**
* The ButtonElement base class will invoke this when the button is clicked.
*/
onClick: function() {
if (!this._attr.registration_url) {
// In the normal case, this will either log you in, or log you out
if (!FB.getAccessToken() || !this._attr.autologoutlink) {
FB.login(FB.bind(this._authCallback, this), {
perms: this._attr.perms,
scope: this._attr.scope
});
} else {
FB.logout(FB.bind(this._authCallback, this));
}
} else {
// If there is a registration url, first log them into Facebook
// and then send them to the registration url
switch (this._attr.status) {
case 'unknown':
FB.ui({ method: 'auth.logintoFacebook' },
FB.bind(function(response) {
// Fetch the status again and then redo the click
FB.bind(FB.getLoginStatus(
this._saveStatus(this._authCallback, /* on_click */ true),
/* force */ true), this);
}, this)
);
break;
case 'notConnected':
case 'not_authorized':
window.top.location = this._attr.registration_url;
break;
case 'connected':
if (!FB.getAccessToken() || !this._attr.autologoutlink) {
// do nothing except call their callback
this._authCallback();
} else {
FB.logout(FB.bind(this._authCallback, this));
}
break;
default:
FB.log('Unknown status: ' + this._attr.status);
}
}
},
/**
* This will be invoked with the result of the FB.login() or FB.logout() to
* pass the result to the developer specified callback if any.
*
* @param response {Object} the auth response object
*/
_authCallback: function(response) {
FB.Helper.invokeHandler(this._attr.onlogin, this, [response]);
},
/**
* A shortcut to save the response status and handle FB.bind().
*
* @param cb {Function} callback
* @param on_click {bool} whether this is an on click event
*/
_saveStatus: function(cb, on_click) {
return FB.bind(function(response) {
if (on_click &&
this._attr.registration_url &&
(this._attr.status == 'notConnected' ||
this._attr.status == 'not_authorized') &&
(response.status == 'notConnected' ||
response.status == 'not_authorized')) {
// user clicked login and is now logged-in but not registered,
// so redirect to registration uri
window.top.location = this._attr.registration_url;
}
this._attr.status = response.status;
if (cb) {
cb = this.bind(cb, this);
return cb(response);
}
}, this);
}
});
/**
* 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.xfbml.name
* @layer xfbml
* @requires fb.type
* fb.xfbml
* fb.dom
* fb.xfbml.element
* fb.data
* fb.helper
* fb.string
*/
/**
* @class FB.XFBML.Name
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.Name', 'XFBML.Element', null, {
/**
* Processes this tag.
*/
process: function() {
FB.copy(this, {
_uid : this.getAttribute('uid'),
_firstnameonly : this._getBoolAttribute('first-name-only'),
_lastnameonly : this._getBoolAttribute('last-name-only'),
_possessive : this._getBoolAttribute('possessive'),
_reflexive : this._getBoolAttribute('reflexive'),
_objective : this._getBoolAttribute('objective'),
_linked : this._getBoolAttribute('linked', true),
_subjectId : this.getAttribute('subject-id')
});
if (!this._uid) {
FB.log('"uid" is a required attribute for <fb:name>');
this.fire('render');
return;
}
var fields = [];
if (this._firstnameonly) {
fields.push('first_name');
} else if (this._lastnameonly) {
fields.push('last_name');
} else {
fields.push('name');
}
if (this._subjectId) {
fields.push('sex');
if (this._subjectId == FB.Helper.getLoggedInUser()) {
this._reflexive = true;
}
}
var data;
// Wait for status to be known
FB.Event.monitor('auth.statusChange', this.bind(function() {
// Is Element still in DOM tree?
if (!this.isValid()) {
this.fire('render');
return true; // Stop processing
}
if (!this._uid || this._uid == 'loggedinuser') {
this._uid = FB.Helper.getLoggedInUser();
}
if (!this._uid) {
return; // dont do anything yet
}
if (FB.Helper.isUser(this._uid)) {
data = FB.Data._selectByIndex(fields, 'user', 'uid', this._uid);
} else {
data = FB.Data._selectByIndex(['name', 'id'], 'profile', 'id',
this._uid);
}
data.wait(this.bind(function(data) {
if (this._subjectId == this._uid) {
this._renderPronoun(data[0]);
} else {
this._renderOther(data[0]);
}
this.fire('render');
}));
}));
},
/**
* Given this name, figure out the proper (English) pronoun for it.
*/
_renderPronoun: function(userInfo) {
var
word = '',
objective = this._objective;
if (this._subjectId) {
objective = true;
if (this._subjectId === this._uid) {
this._reflexive = true;
}
}
if (this._uid == FB.Connect.get_loggedInUser() &&
this._getBoolAttribute('use-you', true)) {
if (this._possessive) {
if (this._reflexive) {
word = 'your own';
} else {
word = 'your';
}
} else {
if (this._reflexive) {
word = 'yourself';
} else {
word = 'you';
}
}
}
else {
switch (userInfo.sex) {
case 'male':
if (this._possessive) {
word = this._reflexive ? 'his own' : 'his';
} else {
if (this._reflexive) {
word = 'himself';
} else if (objective) {
word = 'him';
} else {
word = 'he';
}
}
break;
case 'female':
if (this._possessive) {
word = this._reflexive ? 'her own' : 'her';
} else {
if (this._reflexive) {
word = 'herself';
} else if (objective) {
word = 'her';
} else {
word = 'she';
}
}
break;
default:
if (this._getBoolAttribute('use-they', true)) {
if (this._possessive) {
if (this._reflexive) {
word = 'their own';
} else {
word = 'their';
}
} else {
if (this._reflexive) {
word = 'themselves';
} else if (objective) {
word = 'them';
} else {
word = 'they';
}
}
}
else {
if (this._possessive) {
if (this._reflexive) {
word = 'his/her own';
} else {
word = 'his/her';
}
} else {
if (this._reflexive) {
word = 'himself/herself';
} else if (objective) {
word = 'him/her';
} else {
word = 'he/she';
}
}
}
break;
}
}
if (this._getBoolAttribute('capitalize', false)) {
word = FB.Helper.upperCaseFirstChar(word);
}
this.dom.innerHTML = word;
},
/**
* Handle rendering of the element, using the
* metadata that came with it.
*/
_renderOther: function(userInfo) {
var
name = '',
html = '';
if (this._uid == FB.Helper.getLoggedInUser() &&
this._getBoolAttribute('use-you', true)) {
if (this._reflexive) {
if (this._possessive) {
name = 'your own';
} else {
name = 'yourself';
}
} else {
// The possessive works really nicely this way!
if (this._possessive) {
name = 'your';
} else {
name = 'you';
}
}
}
else if (userInfo) {
// FQLCantSee structures will show as null.
if (null === userInfo.first_name) {
userInfo.first_name = '';
}
if (null === userInfo.last_name) {
userInfo.last_name = '';
}
// Structures that don't exist will return undefined
// (ie. this could happen for an app)
// In that case we just ignore firstnameonly and
// lastnameonly
if (this._firstnameonly && userInfo.first_name !== undefined) {
name = FB.String.escapeHTML(userInfo.first_name);
} else if (this._lastnameonly && userInfo.last_name !== undefined) {
name = FB.String.escapeHTML(userInfo.last_name);
}
if (!name) {
name = FB.String.escapeHTML(userInfo.name);
}
if (name !== '' && this._possessive) {
name += '\'s';
}
}
if (!name) {
name = FB.String.escapeHTML(
this.getAttribute('if-cant-see', 'Facebook User'));
}
if (name) {
if (this._getBoolAttribute('capitalize', false)) {
name = FB.Helper.upperCaseFirstChar(name);
}
if (userInfo && this._linked) {
html = FB.Helper.getProfileLink(userInfo, name,
this.getAttribute('href', null));
} else {
html = name;
}
}
this.dom.innerHTML = html;
}
});
/**
* 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.xfbml.profilepic
* @layer xfbml
* @requires fb.type
* fb.data
* fb.dom
* fb.helper
* fb.string
* fb.xfbml
* fb.xfbml.element
*/
/**
* @class FB.XFBML.ProfilePic
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.ProfilePic', 'XFBML.Element', null, {
/**
* Processes this tag.
*/
process: function() {
var
size = this.getAttribute('size', 'thumb'),
picFieldName = FB.XFBML.ProfilePic._sizeToPicFieldMap[size],
width = this._getPxAttribute('width'),
height = this._getPxAttribute('height'),
style = this.dom.style,
uid = this.getAttribute('uid');
// Check if we need to add facebook logo image
if (this._getBoolAttribute('facebook-logo')) {
picFieldName += '_with_logo';
}
if (width) {
width = width + 'px';
style.width = width;
}
if (height) {
height = height + 'px';
style.height = height;
}
var renderFn = this.bind(function(result) {
var
userInfo = result ? result[0] : null,
imgSrc = userInfo ? userInfo[picFieldName] : null;
if (!imgSrc) {
// Create default
imgSrc = FB.getDomain('cdn') +
FB.XFBML.ProfilePic._defPicMap[picFieldName];
}
// Copy width, height style, and class name of fb:profile-pic down to the
// image element we create
var
styleValue = (
(width ? 'width:' + width + ';' : '') +
(height ? 'height:' + width + ';' : '')
),
html = FB.String.format(
'<img src="{0}" alt="{1}" title="{1}" style="{2}" class="{3}" />',
imgSrc,
userInfo ? FB.String.escapeHTML(userInfo.name) : '',
styleValue,
this.dom.className
);
if (this._getBoolAttribute('linked', true)) {
html = FB.Helper.getProfileLink(
userInfo,
html,
this.getAttribute('href', null)
);
}
this.dom.innerHTML = html;
FB.Dom.addCss(this.dom, 'fb_profile_pic_rendered');
this.fire('render');
});
// Wait for status to be known
FB.Event.monitor('auth.statusChange', this.bind(function() {
//Is Element still in DOM tree
if (!this.isValid()) {
this.fire('render');
return true; // Stop processing
}
if (this.getAttribute('uid', null) == 'loggedinuser') {
uid = FB.Helper.getLoggedInUser();
}
// Is status known?
if (FB._userStatus && uid) {
// Get data
// Use profile if uid is a user, but a page
FB.Data._selectByIndex(
['name', picFieldName],
FB.Helper.isUser(uid) ? 'user' : 'profile',
FB.Helper.isUser(uid) ? 'uid' : 'id',
uid
).wait(renderFn);
} else {
// Render default
renderFn();
}
}));
}
});
FB.provide('XFBML.ProfilePic', {
/**
* Maps field type to placeholder/silhouette image.
*
* This dynamic data is replaced with rsrc.php backed URLs by Haste.
*/
_defPicMap: {
pic : 'pics/s_silhouette.jpg',
pic_big : 'pics/d_silhouette.gif',
pic_big_with_logo : 'pics/d_silhouette_logo.gif',
pic_small : 'pics/t_silhouette.jpg',
pic_small_with_logo : 'pics/t_silhouette_logo.gif',
pic_square : 'pics/q_silhouette.gif',
pic_square_with_logo : 'pics/q_silhouette_logo.gif',
pic_with_logo : 'pics/s_silhouette_logo.gif'
},
/**
* Maps user specified attribute for size to a field type.
*/
_sizeToPicFieldMap: {
n : 'pic_big',
normal : 'pic_big',
q : 'pic_square',
s : 'pic',
small : 'pic',
square : 'pic_square',
t : 'pic_small',
thumb : 'pic_small'
}
});
/**
* 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.xfbml.question
* @layer xfbml
* @requires fb.type
* fb.xfbml.iframewidget
*/
/**
* Implementation for the fb:question tag.
*
* @class FB.XFBML.Question
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Question', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
setupAndValidate: function() {
this._attr = {
channel : this.getChannelUrl(),
api_key : FB._apiKey,
permalink : this.getAttribute('permalink'),
id : this.getAttribute('id'),
width : this._getPxAttribute('width', 400),
height : 0,
questiontext : this.getAttribute('questiontext'),
options : this.getAttribute('options')
};
this.subscribe('xd.firstVote', FB.bind(this._onInitialVote, this));
this.subscribe('xd.vote', FB.bind(this._onChangedVote, this));
return true;
},
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
getUrlBits: function() {
return { name: 'question', params: this._attr };
},
/**
* Invoked when the user initially votes on a question via the Question
* Plugin.
*/
_onInitialVote: function(message) {
FB.Event.fire('question.firstVote', this._attr.permalink, message.vote);
},
/**
* Invoked when the user changes their vote on a question via the
* Question Plugin.
*/
_onChangedVote: function(message) {
FB.Event.fire('question.vote', this._attr.permalink, message.vote);
}
});
/**
* 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.xfbml.recommendations
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget
*/
/**
* Implementation for fb:recommendations tag.
*
* @class FB.XFBML.Recommendations
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Recommendations', 'XFBML.IframeWidget', null, {
_visibleAfter: 'load',
/**
* Refresh the iframe on auth.statusChange events.
*/
_refreshOnAuthChange: true,
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
border_color : this.getAttribute('border-color'),
colorscheme : this.getAttribute('color-scheme'),
filter : this.getAttribute('filter'),
font : this.getAttribute('font'),
action : this.getAttribute('action'),
linktarget : this.getAttribute('linktarget', '_blank'),
max_age : this.getAttribute('max_age'),
header : this._getBoolAttribute('header'),
height : this._getPxAttribute('height', 300),
site : this.getAttribute('site', location.hostname),
width : this._getPxAttribute('width', 300)
};
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'recommendations', params: this._attr };
}
});
/**
* @provides fb.xfbml.recommendationsbar
* @requires fb.anim
* fb.arbiter
* fb.type
* fb.xfbml.iframewidget
* @css fb.css.plugin.recommendationsbar
* @layer xfbml
*/
/**
* 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.
*/
/**
* Implementation for <fb:recommendations-bar /> social plugin tag.
*
* @class FB.XFBML.RecommendationsBar
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass(
'XFBML.RecommendationsBar', 'XFBML.IframeWidget', null, {
getUrlBits: function() {
return { name: 'recommendations_bar', params: this._attr };
},
setupAndValidate: function() {
function interval_queue(interval, func) {
var last_run = 0;
var queued = null;
function run() {
func();
queued = null;
last_run = (new Date()).getTime();
}
return function() { // First Class Functions <3
if (!queued) {
var now = (new Date()).getTime();
if (now - last_run < interval) {
queued = window.setTimeout(run, interval - (now - last_run));
} else {
run();
}
}
return true;
};
}
function validate_trigger(trigger) {
if (trigger.match(/^\d+(?:\.\d+)?%$/)) {
// clip to [0, 100]
var percent = Math.min(Math.max(parseInt(trigger, 10), 0), 100);
trigger = percent / 100;
} else if (trigger != 'manual' && trigger != 'onvisible') {
trigger = 'onvisible';
}
return trigger;
}
function validate_read_time(read_time) {
return Math.max(parseInt(read_time, 10) || 30, 10);
}
function validate_side(side) {
if (side == 'left' || side == 'right') { // Explicitly Provided
return side;
}
return FB._localeIsRtl ? 'left' : 'right'; // Default Guess
}
this._attr = {
channel : this.getChannelUrl(),
api_key : FB._apiKey,
font : this.getAttribute('font'),
colorscheme : this.getAttribute('colorscheme'),
href : FB.URI.resolve(this.getAttribute('href')),
side : validate_side(this.getAttribute('side')),
site : this.getAttribute('site'),
action : this.getAttribute('action'),
ref : this.getAttribute('ref'),
max_age : this.getAttribute('max_age'),
trigger : validate_trigger(this.getAttribute('trigger', '')),
read_time : validate_read_time(this.getAttribute('read_time')),
num_recommendations :
parseInt(this.getAttribute('num_recommendations'), 10) || 2
};
// Right now this is used only to control the use of postMessage in
// FB.Arbiter. This is pretty hacky and should be done properly asap.
FB._inPlugin = true;
this._showLoader = false;
this.subscribe(
'iframe.onload',
FB.bind(
function() {
var span = this.dom.children[0];
span.className = 'fbpluginrecommendationsbar' + this._attr.side;
},
this));
var action = FB.bind(
function() {
FB.Event.unlisten(window, 'scroll', action);
FB.Event.unlisten(document.documentElement, 'click', action);
FB.Event.unlisten(document.documentElement, 'mousemove', action);
window.setTimeout(
FB.bind(
this.arbiterInform,
this,
'platform/plugins/recommendations_bar/action',
null,
FB.Arbiter.BEHAVIOR_STATE),
this._attr.read_time * 1000); // convert s to ms
return true;
}, this);
FB.Event.listen(window, 'scroll', action);
FB.Event.listen(document.documentElement, 'click', action);
FB.Event.listen(document.documentElement, 'mousemove', action);
if (this._attr.trigger == "manual") {
var manual = FB.bind(
function(href) {
if (href == this._attr.href) {
FB.Event.unsubscribe('xfbml.recommendationsbar.read', manual);
this.arbiterInform(
'platform/plugins/recommendations_bar/trigger', null,
FB.Arbiter.BEHAVIOR_STATE);
}
return true;
}, this);
FB.Event.subscribe('xfbml.recommendationsbar.read', manual);
} else {
var trigger = interval_queue(
500,
FB.bind(
function() {
if (this.calculateVisibility()) {
FB.Event.unlisten(window, 'scroll', trigger);
FB.Event.unlisten(window, 'resize', trigger);
this.arbiterInform(
'platform/plugins/recommendations_bar/trigger', null,
FB.Arbiter.BEHAVIOR_STATE);
}
return true;
}, this));
FB.Event.listen(window, 'scroll', trigger);
FB.Event.listen(window, 'resize', trigger);
trigger(); // in case <fb:recommendations-bar /> is already visible
}
this.visible = false;
var visible = interval_queue(
500,
FB.bind(
function() {
if (!this.visible && this.calculateVisibility()) {
this.visible = true;
this.arbiterInform(
'platform/plugins/recommendations_bar/visible');
} else if (this.visible && !this.calculateVisibility()) {
this.visible = false;
this.arbiterInform(
'platform/plugins/recommendations_bar/invisible');
}
return true;
}, this));
FB.Event.listen(window, 'scroll', visible);
FB.Event.listen(window, 'resize', visible);
visible(); // in case <fb:recommendations-bar /> is already visible
this.focused = true;
var toggleFocused = FB.bind(
function() {
this.focused = !this.focused;
return true;
}, this);
FB.Event.listen(window, 'blur', toggleFocused);
FB.Event.listen(window, 'focus', toggleFocused);
this.resize_running = false;
this.animate = false;
this.subscribe(
'xd.signal_animation',
FB.bind(function() { this.animate = true; }, this));
return true;
},
getSize: function() {
// The width here is in Chrome; Firefox is 2px smaller in both cases.
return {
height: 25, width: (this._attr.action == 'recommend' ? 140 : 96) };
},
calculateVisibility: function() {
var fold = document.documentElement.clientHeight; // viewport height
// In Firefox, switching tabs causes viewport scrolling and size changes
// if Firebug is activated, so we avoid changing the visibility state when
// the document is not in focus. This has the unfortunate side effect of
// disabling the read action if the user clicks in the plugin (e.g. to
// expand the plugin or like the page) because these actions will transfer
// focus from this document to the plugn. So, we only perform this check
// if firebug is running.
if (!this.focused && window.console && window.console.firebug) {
return this.visible;
}
switch (this._attr.trigger) {
case "manual":
return false;
case "onvisible":
// <fb:recommendations-bar /> position, relative to the viewport
var elem = this.dom.getBoundingClientRect().top;
return elem <= fold;
default: // "80%", etc.
var scroll = window.pageYOffset || document.body.scrollTop;
var height = document.documentElement.scrollHeight; // doc height
return (scroll + fold) / height >= this._attr.trigger;
}
},
_handleResizeMsg: function(message) {
if (!this.isValid()) {
return;
}
if (message.width) {
this.getIframeNode().style.width = message.width + 'px';
}
if (message.height) {
this._setNextResize(message.height);
this._checkNextResize();
}
this._makeVisible();
},
_setNextResize: function(height) {
this.next_resize = height;
},
_checkNextResize: function() {
if (!this.next_resize || this.resize_running) {
return;
}
var iframe = this.getIframeNode();
var height = this.next_resize;
this.next_resize = null;
if (this.animate) {
this.animate = false; // the signal causes one resize to animate
this.resize_running = true;
FB.Anim.ate(
iframe, { height: height + 'px' }, 330, FB.bind(
function() {
this.resize_running = false;
this._checkNextResize();
}, this));
} else {
iframe.style.height = height + 'px';
}
}
});
/**
* This is the functions that the 3rd party host site can call to manually
* trigger the news.read action. This will work only if the `trigger="manual"`
* attribute is specified on the <fb:recommendations-bar /> tag.
*/
FB.XFBML.RecommendationsBar.markRead = function(href) {
FB.Event.fire('xfbml.recommendationsbar.read', href || window.location.href);
};
/**
* 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.xfbml.registration
* @layer xfbml
* @requires fb.arbiter
* fb.helper
* fb.json
* fb.type
* fb.xfbml.iframewidget
* fb.prelude
* fb.event
*/
/**
* Implementation for fb:registration tag.
*
* @class FB.XFBML.Registration
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.Registration', 'XFBML.IframeWidget', null, {
_visibleAfter: 'immediate',
/** Constants for guessing the height **/
// Logged out is 139 and logged in is 167, so lets make it nice
// for logged in people
_baseHeight: 167,
// How much taller does adding 1 field make the plugin
_fieldHeight: 28,
// Widths below this make the widget render with labels above the fields
_skinnyWidth: 520,
// This one is tough since the text wraps so much, but this is assuming
// 2 lines of text on the top, and 3 lines on the bottom
_skinnyBaseHeight: 173,
// Assuming the labels are above, adding a field height
_skinnyFieldHeight: 52,
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
action : this.getAttribute('action'),
border_color : this.getAttribute('border-color'),
channel_url : this.getChannelUrl(),
client_id : FB._apiKey,
fb_only : this._getBoolAttribute('fb-only', false),
fb_register : this._getBoolAttribute('fb-register', false),
fields : this.getAttribute('fields'),
height : this._getPxAttribute('height'),
redirect_uri : this.getAttribute('redirect-uri', window.location.href),
no_footer : this._getBoolAttribute('no-footer'),
no_header : this._getBoolAttribute('no-header'),
onvalidate : this.getAttribute('onvalidate'),
width : this._getPxAttribute('width', 600),
target : this.getAttribute('target')
};
// All errors will be handled in the iframe, and they show more obvious
// messages than FB.log
if (this._attr.onvalidate) {
this.subscribe('xd.validate', this.bind(function(message) {
var value = FB.JSON.parse(message.value);
var callback = this.bind(function(errors) {
// Send the message back to facebook
FB.Arbiter.inform('Registration.Validation',
{errors: errors, id: message.id},
'parent.frames["' +
this.getIframeNode().name +
'"]',
this._attr.channel_url.substring(0, 5) == "https");
});
// Call their function
var response = FB.Helper.executeFunctionByName(this._attr.onvalidate,
value, callback);
// If they returned anything, call the callback
if (response) {
callback(response);
}
}));
}
this.subscribe('xd.authLogin', FB.bind(this._onAuthLogin, this));
this.subscribe('xd.authLogout', FB.bind(this._onAuthLogout, this));
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._getHeight() };
},
_getHeight: function() {
if (this._attr.height) {
return this._attr.height;
}
var fields;
if (!this._attr.fields) {
// by default, only the name field
fields = ['name'];
} else {
try {
// JSON
fields = FB.JSON.parse(this._attr.fields);
} catch (e) {
// CSV
fields = this._attr.fields.split(/,/);
}
}
if (this._attr.width < this._skinnyWidth) {
return this._skinnyBaseHeight + fields.length * this._skinnyFieldHeight;
} else {
return this._baseHeight + fields.length * this._fieldHeight;
}
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'registration', params: this._attr };
},
/**
* Returns the default domain that should be used for the
* registration plugin being served from the web tier. Because
* of the complexities involved in serving up the registration
* plugin for logged out and logged in HTTPS users, and because
* a near-majority of registration plugins are rendered for logged out
* users, we just always load the registration plugin over https.
* Of particular note, redirects from HTTP to HTTPS for logged-in
* users with account security on cause configuration data to be lost
* because the large amount of configuration data must be sent via POST
* instead of GET because of GET limits.
*/
getDefaultWebDomain: function() {
return 'https_www';
},
/**
* Invoked when the user logs in
*/
_onAuthLogin: function() {
if (!FB.getAuthResponse()) {
FB.getLoginStatus();
}
FB.Helper.fireEvent('auth.login', this);
},
/**
* Invoked when the user logs out
*/
_onAuthLogout: function() {
if (!FB.getAuthResponse()) {
FB.getLoginStatus();
}
FB.Helper.fireEvent('auth.logout', this);
}
});
/**
* 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.xfbml.serverfbml
* @layer xfbml
* @requires fb.type fb.content fb.xfbml.iframewidget fb.auth
*/
/**
* Implementation for fb:serverfbml tag.
*
* @class FB.XFBML.ServerFbml
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.ServerFbml', 'XFBML.IframeWidget', null, {
/**
* Make the iframe visible only when we get the initial resize message.
*/
_visibleAfter: 'resize',
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
// query parameters to the comments iframe
this._attr = {
channel_url : this.getChannelUrl(),
fbml : this.getAttribute('fbml'),
width : this._getPxAttribute('width')
};
// fbml may also be specified as a child script tag
if (!this._attr.fbml) {
var child = this.dom.getElementsByTagName('script')[0];
if (child && child.type === 'text/fbml') {
this._attr.fbml = child.innerHTML;
}
}
// if still no fbml, error
if (!this._attr.fbml) {
FB.log('<fb:serverfbml> requires the "fbml" attribute.');
return false;
}
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'serverfbml', params: this._attr };
}
});
/**
* @provides fb.xfbml.sharebutton
* @requires fb.data
* fb.dom
* fb.helper
* fb.intl
* fb.string
* fb.type
* fb.ui
* fb.xfbml
* fb.xfbml.element
* @css fb.css.sharebutton
* @layer xfbml
*/
/**
* 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.
*/
/**
* Implementation for fb:share-button tag.
* @class FB.XFBML.ShareButton
* @extends FB.XFBML.Element
* @private
*/
FB.subclass('XFBML.ShareButton', 'XFBML.Element', null, {
/**
* Processes this tag.
*/
process: function() {
this._href = this.getAttribute('href', window.location.href);
//TODO: When we turn sharepro on, replace icon_link with button_count
this._type = this.getAttribute('type', 'icon_link');
FB.Dom.addCss(this.dom, 'fb_share_count_hidden'); // start off assuming 0
this._renderButton(true);
},
/**
* Render's the button.
*
* @access private
* @param skipRenderEvent {Boolean} indicate if firing of the render event
* should be skipped. This is useful because the _renderButton() function may
* recursively call itself to do the final render, which is when we want to
* fire the render event.
*/
_renderButton: function(skipRenderEvent) {
if (!this.isValid()) {
this.fire('render');
return;
}
var
contentStr = '',
post = '',
pre = '',
classStr = '',
share = FB.Intl.tx._("Share"),
wrapperClass = '';
switch (this._type) {
case 'icon':
case 'icon_link':
classStr = 'fb_button_simple';
contentStr = (
'<span class="fb_button_text">' +
(this._type == 'icon_link' ? share : '&nbsp;') +
'</span>'
);
skipRenderEvent = false;
break;
case 'link':
contentStr = FB.Intl.tx._("Share on Facebook");
skipRenderEvent = false;
break;
case 'button':
contentStr = '<span class="fb_button_text">' + share + '</span>';
classStr = 'fb_button fb_button_small';
skipRenderEvent = false;
break;
case 'button_count':
contentStr = '<span class="fb_button_text">' + share + '</span>';
post = (
'<span class="fb_share_count_nub_right">&nbsp;</span>' +
'<span class="fb_share_count fb_share_count_right">'+
this._getCounterMarkup() +
'</span>'
);
classStr = 'fb_button fb_button_small';
break;
default:
// box count
contentStr = '<span class="fb_button_text">' + share + '</span>';
pre = (
'<span class="fb_share_count_nub_top">&nbsp;</span>' +
'<span class="fb_share_count fb_share_count_top">' +
this._getCounterMarkup() +
'</span>'
);
classStr = 'fb_button fb_button_small';
wrapperClass = 'fb_share_count_wrapper';
}
var a_id = FB.guid();
this.dom.innerHTML = FB.String.format(
'<span class="{0}">{4}<a id="{1}" class="{2}" ' +
'target="_blank">{3}</a>{5}</span>',
wrapperClass,
a_id,
classStr,
contentStr,
pre,
post
);
var a = document.getElementById(a_id);
a.href = this._href;
a.onclick = function() {
FB.ui({ method: 'stream.share', u: this.href});
return false;
};
if (!skipRenderEvent) {
this.fire('render');
}
},
_getCounterMarkup: function() {
if (!this._count) {
this._count = FB.Data._selectByIndex(
['total_count'],
'link_stat',
'url',
this._href
);
}
var prettyCount = '0';
if (this._count.value !== undefined) {
if (this._count.value.length > 0) {
var c = this._count.value[0].total_count;
if (c > 3) {
// now we want it to be visible
FB.Dom.removeCss(this.dom, 'fb_share_count_hidden');
prettyCount = c >= 10000000 ? Math.round(c/1000000) + 'M' :
(c >= 10000 ? Math.round(c/1000) + 'K' : c);
}
}
} else {
this._count.wait(FB.bind(this._renderButton, this, false));
}
return '<span class="fb_share_count_inner">' + prettyCount + '</span>';
}
});
/**
* 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.xfbml.socialcontext
* @layer xfbml
* @requires fb.type fb.xfbml.iframewidget fb.xfbml.facepile fb.auth
*/
/**
* Implementation for fb:social-context tag.
*
* @class FB.XFBML.SocialContext
* @extends FB.XFBML.IframeWidget
* @private
*/
FB.subclass('XFBML.SocialContext', 'XFBML.IframeWidget', null, {
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
var size = this.getAttribute('size', 'small');
this._attr = {
channel: this.getChannelUrl(),
width: this._getPxAttribute('width', 400),
height: this._getPxAttribute('height', 100),
ref: this.getAttribute('ref'),
size: this.getAttribute('size'),
keywords: this.getAttribute('keywords'),
urls: this.getAttribute('urls')
};
return true;
},
/**
* Get the initial size.
*
* @return {Object} the size
*/
getSize: function() {
return { width: this._attr.width, height: this._attr.height };
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'social_context', params: this._attr };
}
});
/**
* 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.xfbml.subscribe
* @layer xfbml
* @requires fb.type
* fb.xfbml.edgewidget
*/
/**
* Implementation for the fb:subscribe tag.
*
* @class FB.XFBML.Subscribe
* @extends FB.XFBML.EdgeWidget
* @private
*/
FB.subclass('XFBML.Subscribe', 'XFBML.EdgeWidget', null, {
/**
* Do initial attribute processing.
*/
setupAndValidate: function() {
this._attr = {
channel : this.getChannelUrl(),
api_key : FB._apiKey,
font : this.getAttribute('font'),
colorscheme : this.getAttribute('colorscheme', 'light'),
href : this.getAttribute('href'),
ref : this.getAttribute('ref'),
layout : this._getLayout(),
show_faces : this._shouldShowFaces(),
width : this._getWidgetWidth()
};
return true;
},
/**
* Get the URL bits for the iframe.
*
* @return {Object} the iframe URL bits
*/
getUrlBits: function() {
return { name: 'subscribe', params: this._attr };
},
/**
* Returns the width of the widget iframe, taking into
* account the chosen layout, the user supplied width, and
* the min and max values we'll allow. There is much more
* flexibility in how wide the widget is, so a user-supplied
* width just needs to fall within a certain range.
*
* @return {String} the CSS-legitimate width in pixels, as
* with '460px'.
*/
_getWidgetWidth : function() {
var layout = this._getLayout();
var layoutToDefaultWidthMap = {
'standard': 450,
'box_count': 83,
'button_count': 115
};
var defaultWidth = layoutToDefaultWidthMap[layout];
var width = this._getPxAttribute('width', defaultWidth);
var allowedWidths = {
'standard': {'min': 225, 'max': 900},
'box_count': {'min': 43, 'max': 900},
'button_count': {'min': 63, 'max': 900}
};
if (width < allowedWidths[layout].min) {
width = allowedWidths[layout].min;
} else if (width > allowedWidths[layout].max) {
width = allowedWidths[layout].max;
}
return width;
}
});
/**
* A meta component which requires everything.
*
* @nolint
* @provides fb.all
* @requires
* fb.api
* fb.auth
* fb.canvas
* fb.canvas.insights
* fb.canvas.prefetcher
* fb.compat.ui
* fb.compat.xfbml
* fb.data
* fb.frictionless
* fb.init
* fb.init.helper
* fb.nativecalls
* fb.pay
* fb.template_data
* fb.template_ui
* fb.ui.methods
* fb.uri
* fb.xfbml.activity
* fb.xfbml.addprofiletab
* fb.xfbml.addtotimeline
* fb.xfbml.bookmark
* fb.xfbml.comments
* fb.xfbml.commentscount
* fb.xfbml.connectbar
* fb.xfbml.element
* fb.xfbml.facepile
* fb.xfbml.fan
* fb.xfbml.friendpile
* fb.xfbml.like
* fb.xfbml.likebox
* fb.xfbml.livestream
* fb.xfbml.login
* fb.xfbml.loginbutton
* fb.xfbml.name
* fb.xfbml.profilepic
* fb.xfbml.question
* fb.xfbml.recommendations
* fb.xfbml.recommendationsbar
* fb.xfbml.registration
* fb.xfbml.send
* fb.xfbml.serverfbml
* fb.xfbml.sharebutton
* fb.xfbml.socialcontext
* fb.xfbml.subscribe
*/
void(0);
//FB.provide("", {"_domain":{"api":"https:\/\/api.facebook.com\/","api_read":"https:\/\/api-read.facebook.com\/","cdn":"https:\/\/s-static.facebook.com\/","cdn_foreign":"https:\/\/s-static.facebook.com\/","graph":"https:\/\/graph.facebook.com\/","https_cdn":"https:\/\/s-static.facebook.com\/","https_staticfb":"https:\/\/www.facebook.com\/","https_www":"https:\/\/www.facebook.com\/","staticfb":"http:\/\/www.facebook.com\/","www":"https:\/\/www.facebook.com\/","m":"https:\/\/m.facebook.com\/","https_m":"https:\/\/m.facebook.com\/"},"_locale":"en_US","_localeIsRtl":false}, true);
//FB.provide("Flash", {"_minVersions":[[10,3,181,34],[11,0,0]],"_swfPath":"rsrc.php\/v1\/yQ\/r\/f3KaqM7xIBg.swf"}, true);
//FB.provide("XD", {"_xdProxyUrl":"connect\/xd_proxy.php?version=3"}, true);
//FB.provide("Arbiter", {"_canvasProxyUrl":"connect\/canvas_proxy.php?version=3"}, true);
//FB.provide('Auth', {"_xdStorePath":"xd_localstorage\/v2"}, true);
//FB.provide("Canvas.Prefetcher", {"_appIdsBlacklist":[144959615576466],"_sampleRate":500}, true);
//FB.initSitevars = {"parseXFBMLBeforeDomReady":false,"computeContentSizeVersion":0,"enableMobile":1,"enableMobileComments":1,"forceSecureXdProxy":1,"iframePermissions":{"read_stream":false,"manage_mailbox":false,"manage_friendlists":false,"read_mailbox":false,"publish_checkins":true,"status_update":true,"photo_upload":true,"video_upload":true,"sms":false,"create_event":true,"rsvp_event":true,"offline_access":true,"email":true,"xmpp_login":false,"create_note":true,"share_item":true,"export_stream":false,"publish_stream":true,"publish_likes":true,"ads_management":false,"contact_email":true,"access_private_data":false,"read_insights":false,"read_requests":false,"read_friendlists":true,"manage_pages":false,"physical_login":false,"manage_groups":false,"read_deals":false}}; FB.forceOAuth = true; FB.widgetPipeEnabledApps = {"111476658864976":1,"cca6477272fc5cb805f85a84f20fca1d":1,"179150165472010":1}; FB.widgetPipeTagCountThreshold = 4;
//FB.provide("TemplateData", {"_enabled":true}, true);
//FB.provide("TemplateUI", {"_version":19}, true);
//FB.provide("XFBML.ConnectBar", {"imgs":{"buttonUrl":"rsrc.php\/v1\/yY\/r\/h_Y6u1wrZPW.png","missingProfileUrl":"rsrc.php\/v1\/yo\/r\/UlIqmHJn-SK.gif"}}, true);
//FB.provide("XFBML.ProfilePic", {"_defPicMap":{"pic":"rsrc.php\/v1\/yh\/r\/C5yt7Cqf3zU.jpg","pic_big":"rsrc.php\/v1\/yL\/r\/HsTZSDw4avx.gif","pic_big_with_logo":"rsrc.php\/v1\/y5\/r\/SRDCaeCL7hM.gif","pic_small":"rsrc.php\/v1\/yi\/r\/odA9sNLrE86.jpg","pic_small_with_logo":"rsrc.php\/v1\/yD\/r\/k1xiRXKnlGd.gif","pic_square":"rsrc.php\/v1\/yo\/r\/UlIqmHJn-SK.gif","pic_square_with_logo":"rsrc.php\/v1\/yX\/r\/9dYJBPDHXwZ.gif","pic_with_logo":"rsrc.php\/v1\/yu\/r\/fPPR9f2FJ3t.gif"}}, true);