/www/facebook-js-sdk.js
https://github.com/rock14/FacebookConnect · JavaScript · 14638 lines · 9666 code · 741 blank · 4231 comment · 812 complexity · 6daef80396bef2ddb4d70b20ea886c4d MD5 · raw file
- /*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 ' '),
- 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, '"').replace(/'/g, ''');
- },
- /**
- * 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, '"') + '"></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> – ' +
- '<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 : ' ') +
- '</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"> </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"> </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);