1 /* 2 Copyright 2011 Philip Schweiger <pschwei1@gmail.com> 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (at your option) any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 /** 19 * @class FSW - wrapper for the Facebook JavaScript SDK. 20 * (not endorsed by or affiliated with Facebook). 21 * @version 0.2.0 22 * @author Philip Schweiger [pschwei1@gmail.com] 23 */ 24 var fsw = (function () { 25 26 /** 27 * Store basic information about the application 28 * @ignore 29 */ 30 var _appId,_canvasUrl,_tabUrl, 31 32 /** 33 * Check if a value exists in an array 34 * @ignore 35 */ 36 inArray = function(val, array){ 37 for ( var i = 0, length = array.length; i < length; i++ ) { 38 if ( array[i] === val) { 39 return true; 40 } 41 } 42 return false; 43 }, 44 45 /** 46 * Trim whitespace from start and end of string 47 * @ignore 48 */ 49 trim = function(string) { 50 return string.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 51 }, 52 53 /** 54 * Add xmlns:fb to <html> to support rendering of xfbml elements 55 * @ignore 56 */ 57 setXfbml = function(){ 58 var atts = document.documentElement.attributes, 59 fb = document.createAttribute('xmlns:fb'); 60 fb.nodeValue = 'https://www.facebook.com/2008/fbml'; 61 atts.setNamedItem(fb); 62 }; 63 setXfbml(); 64 65 return { 66 /** 67 * Specify a function to execute when the the FB JS SDK is fully loaded. 68 * Wrap all "fsw" methods in this to avoid a race condition. 69 * @example fsw.ready(function(){ 70 * fsw.init(appId); 71 * fsw.doSomething(et cetera); 72 * }); 73 */ 74 ready: function(callback) { 75 var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; 76 script = document.createElement('script'); 77 loaded = false, 78 script.async = 'async'; 79 script.src = 'https://connect.facebook.net/en_US/all.js'; 80 //IE uses onreadystate, normal browsers use onload 81 script.onload = script.onreadystatechange = function() { 82 if (script.readyState) { 83 if (script.readyState == 'loaded' && !loaded) { 84 loaded = true; 85 callback(); 86 } 87 } else { 88 callback(); 89 } 90 } 91 head.insertBefore( script, head.firstChild ); 92 }, 93 94 /** 95 * Calls FB.init() and stores basic app info for later retrieval. 96 * 97 * @memberOf fsw 98 * @param {String} appId application ID 99 * @param {String} [canvasUrl] Fully-qualified app canvas URL 100 * @param {String} [tabUrl] Fully-qualified app tab URL 101 */ 102 init:function(appId, canvasUrl, tabUrl){ 103 if (typeof appId === 'undefined') { 104 throw new Error('fsw.init(): Missing appId'); 105 } 106 107 _appId = appId; 108 _canvasUrl = (typeof canvasUrl === 'undefined')? null : canvasUrl; 109 _tabUrl = (typeof tabUrl === 'undefined')? null : tabUrl; 110 111 FB.init({appId:_appId,status:true,cookie:true,xfbml:true,oauth:true}); 112 }, 113 114 /** 115 * Get Application ID 116 * @memberOf fsw 117 * @returns {String} application id 118 */ 119 getAppId: function(){ 120 return _appId; 121 }, 122 123 /** 124 * Get Canvas URL 125 * Returns null if none set. 126 * @memberOf fsw 127 * @returns {String} canvas url 128 */ 129 getCanvasUrl: function(){ 130 return _canvasUrl; 131 }, 132 133 /** 134 * Get tab URL 135 * Returns null if none set. 136 * @memberOf fsw 137 * @returns {String} tab url 138 */ 139 getTabUrl: function(){ 140 return _tabUrl; 141 }, 142 143 /** 144 * @class Authorization module. Handles login and permissions. 145 * @name fsw.auth 146 */ 147 auth: { 148 /** 149 * Bind FB.login() to a specified DOM element 150 * Only binds to elements that have an ID. 151 * @memberOf fsw.auth 152 * @param {Strging} triggerId ID of element to bind to 153 * @param {Function} callback function to launch after login 154 * @param {String} [permissions] Comma separated list, 155 * see http://bit.ly/hOUAQD and http://bit.ly/cjEOKB 156 */ 157 createBtn: function(triggerId,callback, permissions){ 158 var trigger = document.getElementById(triggerId), 159 perms = (typeof permissions !== 'undefined')? {perms:permissions}: null; 160 if (!trigger) { 161 throw new Error('fsw.auth.createBtn: No element with ID "'+triggerId+'" exists in DOM'); 162 } 163 164 if (typeof callback !== 'function') { 165 throw new Error('fsw.auth.createBtn: must pass a callback function'); 166 } 167 168 trigger.onclick = function(){ 169 FB.login(callback,perms); 170 return false; 171 }; 172 173 } 174 }, 175 176 /** 177 * @class Like module. Handles the like button and related events. 178 * @name fsw.like 179 */ 180 like: { 181 /** 182 * Constructor for likeBtn instances 183 * @memberOf fsw.like 184 * @private 185 * @returns {LikeBtn} 186 */ 187 likeBtn: function(container, target, opts, layoutOpts){ 188 var action = (opts.action === 'recommend')? 'recommend' : 'like', 189 colorscheme = (opts.colorscheme === 'dark')? 'dark' : 'light', 190 faces = opts.faces === true, 191 font = opts.font || 'arial', 192 layout = (inArray(opts.layout,layoutOpts))? opts.layout : 'standard', 193 ref = (typeof opts.ref === 'string')? opts.ref: '', 194 send = opts.send === true, 195 width = (parseInt(opts.width,10) > 0)? parseInt(opts.width,10) : 450, 196 197 fblike = document.createElement('fb:like'); 198 199 fblike.setAttribute('href',target); 200 fblike.setAttribute('action',action); 201 fblike.setAttribute('send',send); 202 fblike.setAttribute('width',width); 203 fblike.setAttribute('show_faces',faces); 204 fblike.setAttribute('font',font); 205 fblike.setAttribute('layout',layout); 206 fblike.setAttribute('colorscheme',colorscheme); 207 fblike.setAttribute('ref',ref); 208 209 container.appendChild(fblike); 210 211 FB.XFBML.parse(); 212 213 return this; 214 }, 215 216 /** 217 * Create a FB "like" button 218 * Inserts a like button in the specified DOM element. 219 * Containing element must have in ID. 220 * @memberOf fsw.like 221 * @param {String} containerId ID of the DOM element in which to insert the Like button 222 * @param {String} target URL to Like 223 * @param [options] Additional information passed to the like button 224 * @param {String} options.action Default: like - Alts: recommend 225 * @param {String} options.colorscheme Default: light - Alts: dark 226 * @param {Boolean} options.faces Default: false - Show fans' profile pics 227 * @param {String} options.font Default: arial - Alts: lucida grande | segoe ui | tahoma | trebuchet ms | verdana 228 * @param {String} options.layout Default: standard - Alts: button_count | box_count 229 * @param {String} options.ref a label for tracking referrals 230 * @param {Boolean} options.send Default: false - Include a "send" button? 231 * @param {Number} options.width Default: 420 - Width, in pixels, of plugin 232 */ 233 createBtn: function(containerId, target, options) { 234 var container = document.getElementById(containerId), 235 err = new Error(), 236 opts = options || {}, 237 layoutOpts = ['standard','button_count','box_count']; 238 239 if (!container) { 240 throw new Error('fsw.like.createBtn: No element with ID "'+containerId+'" exists in DOM'); 241 } 242 243 if (target === undefined) { 244 err.message = 'fws.like.createBtn(): Missing "like" target'; 245 throw err; 246 } 247 return new this.likeBtn(container, target, opts, layoutOpts); 248 } 249 }, 250 251 /** 252 * @class Send module. Handles the send button and related events. 253 * @name fsw.send 254 */ 255 send: { 256 /** 257 * Constructor for sendBtn instances 258 * @memberOf fsw.send 259 * @private 260 * @returns {SendBtn} 261 */ 262 sendBtn: function(container, target, opts){ 263 264 var colorscheme = (opts.colorscheme === 'dark')? 'dark' : 'light', 265 font = opts.font || 'arial', 266 ref = (typeof opts.ref === 'string')? opts.ref : '', 267 268 fbsend = document.createElement('fb:send'); 269 270 fbsend.setAttribute('href',target); 271 fbsend.setAttribute('font',font); 272 fbsend.setAttribute('colorscheme',colorscheme); 273 fbsend.setAttribute('ref',ref); 274 275 container.appendChild(fbsend); 276 277 FB.XFBML.parse(); 278 279 return this; 280 }, 281 282 /** 283 * Create a FB "send" button 284 * Inserts a send button in the specified DOM element. 285 * Containing element must have in ID. 286 * @memberOf fsw.send 287 * @param {String} containerId ID of the DOM element in which to insert the Send button 288 * @param {String} target URL to Like 289 * @param [options] Additional information passed to the like button 290 * @param {String} options.colorscheme Default: light - Alts: dark 291 * @param {String} options.font Default: arial - Alts: lucida grande | segoe ui | tahoma | trebuchet ms | verdana 292 * @param {String} options.ref a label for tracking referrals 293 */ 294 createBtn: function(containerId, target, options) { 295 var container = document.getElementById(containerId), 296 err = new Error(), 297 opts = options || {}; 298 299 if (!container) { 300 throw new Error('fsw.send.createBtn: No element with ID "'+containerId+'" exists in DOM'); 301 } 302 303 if (target === undefined) { 304 err.message = 'fsw.send.createBtn(): Missing "send" target'; 305 throw err; 306 } 307 308 return new this.sendBtn(container, target, opts); 309 } 310 }, 311 312 /** 313 * @class Stream module. Handles stream interactions, such as posting to Wall. 314 * @name fsw.stream 315 */ 316 stream: { 317 /** 318 * Post to current user's wall/stream 319 * @memberOf fsw.stream 320 * @param {Object} [opts] Options for customizing the wall post 321 * @param {String} [opts.title] Title of the post. Defaults to blank. 322 * @param {String} [opts.subtitle] Subtitle of the post 323 * @param {String} [opts.link] The link attached to this post. 324 * Must be within your app's domain. 325 * @param {String} [opts.description] App-supplied text to appear in the post. 326 * Defaults to blank. If no link and no media, description will not appear. 327 * @param {String} [opts.media] URL of media to display with the post. FB 328 * will try to pull an image from linked page if this is missing. If you pass a swf, you must also pass a thumbnail image in comma-separated format, eg "http://domain/movie.swf, http://domain/image.jpg" 329 * @param {String} [opts.action] Comma-separated pair of verb and link, 330 * eg "action,http://www.example.com" Link must be within your app's domain, 331 * and name becomes lowercase alphanumeric. 332 * @param {Function} [callback] Function to call after user sends or cancels 333 * post. Returns null on user cancel, post id on user send. 334 */ 335 post: function(options, callback){ 336 var opts = options || {}, 337 name = opts.title || ' ', 338 cap = opts.subtitle || ' ', 339 link = opts.link || null, 340 source = opts.media || null, 341 desc = opts.description || ' ', 342 action = (function(){ 343 if (opts.action) { 344 var action = opts.action.split(','); 345 return {name:action[0],link:trim(action[1])}; 346 } else { 347 return null; 348 } 349 }()), 350 cb = (typeof callback === 'function')? callback : null, 351 352 fbparams = { 353 actions:action, 354 caption:cap, 355 description:desc, 356 link:link, 357 method:'feed', 358 name:name, 359 source:source 360 }; 361 //If this is a swf, set a thumbnail 362 if (source && source.indexOf('.swf') > -1) { 363 364 if (source.indexOf(',') === -1) { 365 throw new Error('fsw.stream.post(): Missing thumbnail for swf'); 366 } else { 367 var parts = source.split(','); 368 fbparams.source = parts[0]; 369 fbparams.picture = trim(parts[1]); 370 } 371 } 372 373 FB.ui(fbparams,cb); 374 } 375 }, 376 377 /** 378 * @class Request module. Sends app requests. 379 * @name fsw.request 380 */ 381 request: { 382 /** 383 *Send an app request to a specific friend, or to several friends. 384 *Note that this module does not address server-side handling of app requests. 385 *@memberOf fsw.request 386 *@param {Object} opts 387 *@param {String} opts.message Request message 388 *@param {String} [opts.to] A FBID or FB user name to send the request to. Must be a friend of the user. 389 *@param {String} [opts.data] Data to send along with the request. Better explanation TK. 390 *@param {Function}[callback] Function to call after user sends or cancels post. Returns null on user cancel, request id on user send. 391 */ 392 send: function(opts, callback){ 393 if (typeof opts.message !== 'string') { 394 throw new Error('Must pass opts.message as a string'); 395 } 396 397 var data = opts.data || null, 398 to = opts.to || null; 399 400 callback = (typeof callback === 'function')? callback : null; 401 402 FB.ui( 403 { 404 data:data, 405 message:opts.message, 406 method:'apprequests', 407 to:to 408 }, 409 callback 410 ); 411 } 412 } 413 } 414 415 }()); 416 417 /** 418 * Bind to "like" buttons' like/unlike events 419 * @memberOf fsw.like 420 * @param {Function} onLike callback when user "likes" using FB like button 421 * @param {Function} onUnlike callback when user "unlikes" using FB like button 422 * 423 * @example var myButton = fsw.like.createBtn(containerId,target); 424 * myButton.bind(onLike, onUnlike); 425 */ 426 fsw.like.likeBtn.prototype.bind = function(onLike,onUnlike){ 427 428 if (typeof onLike !== 'function' || typeof onUnlike !== 'function') { 429 throw new Error("must pass an onLike and an onUnlike callback as function"); 430 } 431 432 FB.Event.subscribe('edge.create',onLike); 433 FB.Event.subscribe('edge.remove',onUnlike); 434 435 }; 436 437 /** 438 * Bind to "send" buttons' like/unlike events 439 * @memberOf fsw.send 440 * @param {Function} onSend callback when user "sends" using FB send button 441 * 442 * @example var myButton = fsw.send.createBtn(containerId,target); 443 * myButton.bind(onSend); 444 */ 445 fsw.send.sendBtn.prototype.bind = function(onSend){ 446 447 if (typeof onSend !== 'function') { 448 throw new Error("must pass an onSend callback as function"); 449 } 450 451 FB.Event.subscribe('message.send',onSend); 452 }; 453 454 /** 455 * On <body> load, create and insert #fb-root into the DOM. Don't wait for init(); 456 * Note - I'm a bit worried this sets up a race condition against the FB libary loading, but 457 * in nearly all cases the body will load much faster than the FB library will, so I _think_ we're ok. 458 * @ignore 459 */ 460 fsw.isBodyLoaded = function(callback) { 461 var body = document.getElementsByTagName('body'); 462 if (body.length > 0) { 463 var fbroot = document.createElement('div'); 464 fbroot.id = 'fb-root'; 465 body[0].appendChild(fbroot); 466 } else { 467 setTimeout(fsw.isBodyLoaded,1,callback); 468 } 469 } 470 fsw.isBodyLoaded();