1 /** 2 * Copyright (c) 2014, salesforce.com, inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without modification, are permitted provided 6 * that the following conditions are met: 7 * 8 * Redistributions of source code must retain the above copyright notice, this list of conditions and the 9 * following disclaimer. 10 * 11 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and 12 * the following disclaimer in the documentation and/or other materials provided with the distribution. 13 * 14 * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or 15 * promote products derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 19 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 * POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 /** 28 *@namespace Sfdc.canvas.client 29 *@name Sfdc.canvas.client 30 */ 31 (function ($$) { 32 33 "use strict"; 34 35 var pversion, cversion = "31.0"; 36 37 var module = (function() /**@lends module */ { 38 39 var purl; 40 41 // returns the url of the Parent Window 42 function getTargetOrigin(to) { 43 var h; 44 if (to === "*") {return to;} 45 if (!$$.isNil(to)) { 46 h = $$.stripUrl(to); 47 purl = $$.startsWithHttp(h, purl); 48 if (purl) {return purl;} 49 } 50 // This relies on the parent passing it in. This may not be there as the client can do a redirect. 51 h = $$.document().location.hash; 52 if (h) { 53 h = decodeURIComponent(h.replace(/^#/, '')); 54 purl = $$.startsWithHttp(h, purl); 55 } 56 return purl; 57 } 58 59 // The main cross domain callback handler 60 function xdCallback(data) { 61 if (data) { 62 if (submodules[data.type]) { 63 submodules[data.type].callback(data); 64 } 65 // Just ignore... 66 } 67 } 68 69 var submodules = (function () { 70 71 var cbs = [], seq = 0, autog = true; 72 73 // Functions common to submodules... 74 75 function postit(clientscb, message) { 76 var wrapped, to, c; 77 78 // need to keep a mapping from request to callback, otherwise 79 // wrong callbacks get called. Unfortunately, this is the only 80 // way to handle this as postMessage acts more like topic/queue. 81 // limit the sequencers to 100 avoid out of memory errors 82 seq = (seq > 100) ? 0 : seq + 1; 83 cbs[seq] = clientscb; 84 wrapped = {seq : seq, src : "client", clientVersion : cversion, parentVersion: pversion, body : message}; 85 86 c = message && message.config && message.config.client; 87 to = getTargetOrigin($$.isNil(c) ? null : c.targetOrigin); 88 if ($$.isNil(to)) { 89 throw "ERROR: targetOrigin was not supplied and was not found on the hash tag, this can result from a redirect or link to another page."; 90 } 91 $$.console.log("posting message ", {message : wrapped, to : to}); 92 $$.xd.post(wrapped, to, parent); 93 } 94 95 function validateClient(client, cb) { 96 var msg; 97 98 client = client || $$.oauth && $$.oauth.client(); 99 100 if ($$.isNil(client) || $$.isNil(client.oauthToken)) { 101 msg = {status : 401, statusText : "Unauthorized" , parentVersion : pversion, payload : "client or client.oauthToken not supplied"}; 102 } 103 if ($$.isNil(client.instanceId) || $$.isNil(client.targetOrigin)) { 104 msg = {status : 400, statusText : "Bad Request" , parentVersion : pversion, payload : "client.instanceId or client.targetOrigin not supplied"}; 105 } 106 if (!$$.isNil(msg)) { 107 if ($$.isFunction(cb)) { 108 cb(msg); 109 return false; 110 } 111 else { 112 throw msg; 113 } 114 } 115 return true; 116 } 117 118 // Submodules... 119 120 var event = (function() { 121 var subscriptions = {}, STR_EVT = "sfdc.streamingapi"; 122 123 function validName(name, res) { 124 var msg, r = $$.validEventName(name, res); 125 if (r !== 0) { 126 msg = {1 : "Event names can only contain one namespace", 127 2 : "Namespace has already been reserved", 128 3 : "Event name contains invalid characters" 129 }; 130 throw msg[r]; 131 } 132 } 133 134 function findSubscription(event) { 135 var s, name = event.name; 136 137 if (name === STR_EVT) { 138 if (!$$.isNil(subscriptions[name])) { 139 s = subscriptions[name][event.params.topic]; 140 } 141 } else { 142 s = subscriptions[name]; 143 } 144 145 if (!$$.isNil(s) && ($$.isFunction(s.onData) || $$.isFunction(s.onComplete))) { 146 return s; 147 } 148 149 return null; 150 } 151 152 return { 153 callback : function (data) { 154 var event = data.payload, 155 subscription = findSubscription(event), 156 func; 157 158 if (!$$.isNil(subscription)) { 159 if (event.method === "onData") { 160 func = subscription.onData; 161 } else if (event.method === "onComplete") { 162 func = subscription.onComplete; 163 } 164 165 if (!$$.isNil(func) && $$.isFunction(func)) { 166 func(event.payload); 167 } 168 } 169 }, 170 171 /** 172 * @description Subscribes to parent or custom events. Events 173 * with the namespaces 'canvas', 'sfdc', 'force', 'salesforce', and 'chatter' are reserved by Salesforce. 174 * Developers can choose their own namespace and event names. 175 * Event names must be in the form <code>namespace.eventname</code>. 176 * @public 177 * @name Sfdc.canvas.client#subscribe 178 * @function 179 * @param {client} client The object from the signed request 180 * @param {Object} s The subscriber object or array of objects with name and callback functions 181 * @example 182 * // Subscribe to the parent window onscroll event. 183 * Sfdc.canvas(function() { 184 * sr = JSON.parse('<%=signedRequestJson%>'); 185 * // Capture the onScrolling event of the parent window. 186 * Sfdc.canvas.client.subscribe(sr.client, 187 * {name : 'canvas.scroll', onData : function (event) { 188 * console.log("Parent's contentHeight; " + event.heights.contentHeight); 189 * console.log("Parent's pageHeight; " + event.heights.pageHeight); 190 * console.log("Parent's scrollTop; " + event.heights.scrollTop); 191 * console.log("Parent's contentWidth; " + event.widths.contentWidth); 192 * console.log("Parent's pageWidth; " + event.widths.pageWidth); 193 * console.log("Parent's scrollLeft; " + event.widths.scrollLeft); 194 * }} 195 * ); 196 * }); 197 * 198 * @example 199 * // Subscribe to a custom event. 200 * Sfdc.canvas(function() { 201 * sr = JSON.parse('<%=signedRequestJson%>'); 202 * Sfdc.canvas.client.subscribe(sr.client, 203 * {name : 'mynamespace.someevent', onData : function (event) { 204 * console.log("Got custom event ", event); 205 * }} 206 * ); 207 * }); 208 * 209 * @example 210 * // Subscribe to multiple events 211 * Sfdc.canvas(function() { 212 * sr = JSON.parse('<%=signedRequestJson%>'); 213 * Sfdc.canvas.client.subscribe(sr.client, [ 214 * {name : 'mynamespace.someevent1', onData : handler1}, 215 * {name : 'mynamespace.someevent2', onData : handler2}, 216 * ]); 217 * }); 218 * 219 * @example 220 * //Subscribe to Streaming API events. 221 * //The PushTopic to subscribe to must be passed in. 222 * //The 'onComplete' method may be defined, 223 * //and will fire when the subscription is complete. 224 * Sfdc.canvas(function() { 225 * sr = JSON.parse('<%=signedRequestJson%>'); 226 * var handler1 = function(){ console.log("onData done");}, 227 * handler2 = function(){ console.log("onComplete done");}; 228 * Sfdc.canvas.client.subscribe(sr.client, 229 * {name : 'sfdc.streamingapi', params:{topic:"/topic/InvoiceStatements"}}, 230 * onData : handler1, onComplete : handler2} 231 * ); 232 * }); 233 * 234 * 235 */ 236 subscribe : function(client, s) { 237 var subs = {}; 238 239 if ($$.isNil(s) || (!validateClient(client))) { 240 throw "precondition fail"; 241 } 242 243 $$.each($$.isArray(s) ? s : [s], function (v) { 244 if (!$$.isNil(v.name)) { 245 validName(v.name, ["canvas", "sfdc"]); 246 247 if (v.name === STR_EVT) { 248 if (!$$.isNil(v.params) && !$$.isNil(v.params.topic)) { 249 if ($$.isNil(subscriptions[v.name])) { 250 subscriptions[v.name] = {}; 251 } 252 subscriptions[v.name][v.params.topic] = v; 253 } else { 254 throw "[" +STR_EVT +"] topic is missing"; 255 } 256 } else { 257 subscriptions[v.name] = v; 258 } 259 260 subs[v.name] = { 261 params : v.params 262 }; 263 } else { 264 throw "subscription does not have a 'name'"; 265 } 266 }); 267 if (!client.isVF) { 268 postit(null, {type : "subscribe", config : {client : client}, subscriptions : subs}); 269 } 270 }, 271 /** 272 * @description Unsubscribes from parent or custom events. 273 * @public 274 * @name Sfdc.canvas.client#unsubscribe 275 * @function 276 * @param {client} client The object from the signed request 277 * @param {Object} s The events to unsubscribe from 278 * @example 279 * //Unsubscribe from the canvas.scroll method. 280 * Sfdc.canvas(function() { 281 * sr = JSON.parse('<%=signedRequestJson%>'); 282 * Sfdc.canvas.client.unsubscribe(sr.client, "canvas.scroll"); 283 *}); 284 * 285 * @example 286 * //Unsubscribe from the canvas.scroll method by specifying the object name. 287 * Sfdc.canvas(function() { 288 * sr = JSON.parse('<%=signedRequestJson%>'); 289 * Sfdc.canvas.client.unsubscribe(sr.client, {name : "canvas.scroll"}); 290 *}); 291 * 292 * @example 293 * //Unsubscribe from multiple events. 294 * Sfdc.canvas(function() { 295 * sr = JSON.parse('<%=signedRequestJson%>'); 296 * Sfdc.canvas.client.unsubscribe(sr.client, ['canvas.scroll', 'foo.bar']); 297 *}); 298 * 299 * @example 300 * //Unsubscribe from Streaming API events. 301 * //The PushTopic to unsubscribe from must be passed in. 302 * Sfdc.canvas(function() { 303 * sr = JSON.parse('<%=signedRequestJson%>'); 304 * Sfdc.canvas.client.unsubscribe(sr.client, {name : 'sfdc.streamingapi', 305 * params:{topic:"/topic/InvoiceStatements"}}); 306 *}); 307 */ 308 unsubscribe : function(client, s) { 309 // client can pass in the handler object or just the name 310 var subs = {}; 311 312 if ($$.isNil(s) || !validateClient(client)) { 313 throw "PRECONDITION FAIL: need fo supply client and event name"; 314 } 315 316 if ($$.isString(s)) { 317 subs[s] = {}; 318 delete subscriptions[s]; 319 } 320 else { 321 $$.each($$.isArray(s) ? s : [s], function (v) { 322 var name = v.name ? v.name : v; 323 validName(name, ["canvas", "sfdc"]); 324 subs[name] = { 325 params : v.params 326 }; 327 if (name === STR_EVT) { 328 if(!$$.isNil(subscriptions[name])) { 329 if (!$$.isNil(subscriptions[name][v.params.topic])) { 330 delete subscriptions[name][v.params.topic]; 331 } 332 if ($$.size(subscriptions[name]) <= 0) { 333 delete subscriptions[name]; 334 } 335 } 336 } else { 337 delete subscriptions[name]; 338 } 339 }); 340 } 341 if (!client.isVF) { 342 postit(null, {type : "unsubscribe", config : {client : client}, subscriptions : subs}); 343 } 344 }, 345 /** 346 * @description Publishes a custom event. Events are published to all subscribing canvas applications 347 * on the same page, regardless of domain. Choose a unique namespace so the event doesn't collide with other 348 * application events. Events can have payloads of arbitrary JSON objects. 349 * @public 350 * @name Sfdc.canvas.client#publish 351 * @function 352 * @param {client} client The object from the signed request 353 * @param {Object} e The event to publish 354 * @example 355 * // Publish the foo.bar event with the specified payload. 356 * Sfdc.canvas(function() { 357 * sr = JSON.parse('<%=signedRequestJson%>'); 358 * Sfdc.canvas.client.publish(sr.client, 359 * {name : "foo.bar", payload : {some : 'stuff'}}); 360 *}); 361 */ 362 publish : function(client, e) { 363 if (!$$.isNil(e) && !$$.isNil(e.name)) { 364 validName(e.name); 365 if (validateClient(client)) { 366 postit(null, {type : "publish", config : {client : client}, event : e}); 367 } 368 } 369 } 370 }; 371 }()); 372 373 var callback = (function() { 374 return { 375 callback : function (data) { 376 // If the server is telling us the access_token is invalid, wipe it clean. 377 if (data.status === 401 && 378 $$.isArray(data.payload) && 379 data.payload[0].errorCode && 380 data.payload[0].errorCode === "INVALID_SESSION_ID") { 381 // Session has expired logout. 382 if ($$.oauth) {$$.oauth.logout();} 383 } 384 // Find appropriate client callback an invoke it. 385 if ($$.isFunction(cbs[data.seq])) { 386 if (!$$.isFunction(cbs[data.seq])) { 387 alert("not function"); 388 } 389 cbs[data.seq](data); 390 } 391 else { 392 // This can happen when the user switches out canvas apps real quick, 393 // before the request from the last canvas app have finish processing. 394 // We will ignore any of these results as the canvas app is no longer active to 395 // respond to the results. 396 } 397 } 398 }; 399 }()); 400 401 var services = (function() { 402 403 var sr; 404 405 return { 406 /** 407 * @description Performs a cross-domain, asynchronous HTTP request. 408 <br>Note: this method shouldn't be used for same domain requests. 409 * @param {String} url The URL to which the request is sent 410 * @param {Object} settings A set of key/value pairs to configure the request 411 <br>The success setting is required at minimum and should be a callback function 412 * @config {String} [client] The required client context {oauthToken: "", targetOrigin : "", instanceId : ""} 413 * @config {String} [contentType] "application/json" 414 * @config {String} [data] The request body 415 * @config {String} [headers] request headers 416 * @config {String} [method="GET"] The type of AJAX request to make 417 * @config {Function} [success] Callback for all responses from the server (failure and success). Signature: success(response); interesting fields: [response.data, response.responseHeaders, response.status, response.statusText} 418 * @config {Boolean} [async=true] Asynchronous: only <code>true</code> is supported. 419 * @name Sfdc.canvas.client#ajax 420 * @function 421 * @throws An error if the URL is missing or the settings object doesn't contain a success callback function. 422 * @example 423 * //Posting to a Chatter feed: 424 * var sr = JSON.parse('<%=signedRequestJson%>'); 425 * var url = sr.context.links.chatterFeedsUrl+"/news/" 426 * +sr.context.user.userId+"/feed-items"; 427 * var body = {body : {messageSegments : [{type: "Text", text: "Some Chatter Post"}]}}; 428 * Sfdc.canvas.client.ajax(url, 429 * {client : sr.client, 430 * method: 'POST', 431 * contentType: "application/json", 432 * data: JSON.stringify(body), 433 * success : function(data) { 434 * if (201 === data.status) { 435 * alert("Success" 436 * } 437 * } 438 * }); 439 * @example 440 * // Gets a list of Chatter users: 441 * // Paste the signed request string into a JavaScript object for easy access. 442 * var sr = JSON.parse('<%=signedRequestJson%>'); 443 * // Reference the Chatter user's URL from Context.Links object. 444 * var chatterUsersUrl = sr.context.links.chatterUsersUrl; 445 * 446 * // Make an XHR call back to Salesforce through the supplied browser proxy. 447 * Sfdc.canvas.client.ajax(chatterUsersUrl, 448 * {client : sr.client, 449 * success : function(data){ 450 * // Make sure the status code is OK. 451 * if (data.status === 200) { 452 * // Alert with how many Chatter users were returned. 453 * alert("Got back " + data.payload.users.length + 454 * " users"); // Returned 2 users 455 * } 456 * })}; 457 */ 458 ajax : function (url, settings) { 459 460 var ccb, config, defaults; 461 462 if (!url) { 463 throw "PRECONDITION ERROR: url required with AJAX call"; 464 } 465 if (!settings || !$$.isFunction(settings.success)) { 466 throw "PRECONDITION ERROR: function: 'settings.success' missing."; 467 } 468 if (! validateClient(settings.client, settings.success)) { 469 return; 470 } 471 472 ccb = settings.success; 473 defaults = { 474 method: 'GET', 475 async: true, 476 contentType: "application/json", 477 headers: {"Authorization" : "OAuth " + settings.client.oauthToken, 478 "Accept" : "application/json"}, 479 data: null 480 }; 481 config = $$.extend(defaults, settings || {}); 482 483 // Remove any listeners as functions cannot get marshaled. 484 config.success = undefined; 485 config.failure = undefined; 486 // Don't allow the client to set "*" as the target origin. 487 if (config.client.targetOrigin === "*") { 488 config.client.targetOrigin = null; 489 } 490 else { 491 // We need to set this here so we can validate the origin when we receive the call back 492 purl = $$.startsWithHttp(config.targetOrigin, purl); 493 } 494 postit(ccb, {type : "ajax", url : url, config : config}); 495 }, 496 /** 497 * @description Returns the context for the current user and organization. 498 * @public 499 * @name Sfdc.canvas.client#ctx 500 * @function 501 * @param {Function} clientscb The callback function to run when the call to ctx completes 502 * @param {Object} client The signedRequest.client. 503 * @example 504 * // Gets context in the canvas app. 505 * 506 * function callback(msg) { 507 * if (msg.status !== 200) { 508 * alert("Error: " + msg.status); 509 * return; 510 * } 511 * alert("Payload: ", msg.payload); 512 * } 513 * var ctxlink = Sfdc.canvas.byId("ctxlink"); 514 * var client = Sfdc.canvas.oauth.client(); 515 * ctxlink.onclick=function() { 516 * Sfdc.canvas.client.ctx(callback, client)}; 517 * } 518 */ 519 ctx : function (clientscb, client) { 520 if (validateClient(client, clientscb)) { 521 postit(clientscb, {type : "ctx", accessToken : client.oauthToken, config : {client : client}}); 522 } 523 }, 524 /** 525 * @public 526 * @function 527 * @name Sfdc.canvas.client#token 528 * @description Stores or gets the oauth token in a local javascript variable. Note, if longer term 529 * (survive page refresh) storage is needed store the oauth token on the server side. 530 * @param {String} t oauth token, if supplied it will be stored in a volatile local JS variable. 531 * @returns {Object} the oauth token. 532 */ 533 token : function(t) { 534 return $$.oauth && $$.oauth.token(t); 535 }, 536 /** 537 * @description Returns the current version of the client. 538 * @public 539 * @function 540 * @name Sfdc.canvas.client#version 541 * @returns {Object} {clientVersion : "29.0", parentVersion : "29.0"}. 542 */ 543 version : function() { 544 return {clientVersion: cversion, parentVersion : pversion}; 545 }, 546 /** 547 * @function 548 * @public 549 * @name Sfdc.canvas.client#signedrequest 550 * @description Temporary storage for the signed request. An alternative for users storing SR in 551 * a global variable. Note: if you would like a new signed request take a look at refreshSignedRequest(). 552 * @param {Object} s signedrequest to be temporarily stored in Sfdc.canvas.client object. 553 * @returns {Object} the value previously stored 554 */ 555 signedrequest : function(s) { 556 if (arguments.length > 0) { 557 sr = s; 558 } 559 return sr; 560 }, 561 /** 562 * @function 563 * @public 564 * @name Sfdc.canvas.client#refreshSignedRequest 565 * @description Refresh the signed request. Obtain a new signed request on demand. Note the 566 * authentication mechanism of the canvas app must be set to SignedRequest and not OAuth. 567 * @param {Function} clientscb The client's callback function to receive the base64 encoded signed request. 568 * @example 569 * // Gets a signed request on demand. 570 * Sfdc.canvas.client.refreshSignedRequest(function(data) { 571 * if (data.status === 200) { 572 * var signedRequest = data.payload.response; 573 * var part = signedRequest.split('.')[1]; 574 * var obj = JSON.parse(Sfdc.canvas.decode(part)); 575 * } 576 * } 577 */ 578 refreshSignedRequest : function(clientscb) { 579 var id = window.name.substring("canvas-frame-".length), 580 client = {oauthToken : "null", instanceId : id, targetOrigin : "*"}; 581 postit(clientscb, {type : "refresh", accessToken : client.oauthToken, config : {client : client}}); 582 }, 583 /** 584 * @public 585 * @function 586 * @name Sfdc.canvas.client#repost 587 * @description Repost the signed request. Instruct the parent window to initiate a new post to the 588 * canvas url. Note the authentication mechanism of the canvas app must be set to SignedRequest and not OAuth. 589 * @param {Boolean}[refresh=false] Refreshes the signed request when set to true. 590 * @example 591 * // Gets a signed request on demand, without refreshing the signed request. 592 * Sfdc.canvas.client.repost(); 593 * // Gets a signed request on demand, first by refreshing the signed request. 594 * Sfdc.canvas.client.repost({refresh : true}); 595 */ 596 repost : function(refresh) { 597 var id = window.name.substring("canvas-frame-".length), 598 client = {oauthToken : "null", instanceId : id, targetOrigin : "*"}, 599 r= refresh || false; 600 postit(null, {type : "repost", accessToken : client.oauthToken, config : {client : client}, refresh : r}); 601 } 602 603 }; 604 }()); 605 606 var frame = (function() { 607 return { 608 /** 609 * @public 610 * @name Sfdc.canvas.client#size 611 * @function 612 * @description Returns the current size of the iFrame. 613 * @return {Object}<br> 614 * <code>heights.contentHeight</code>: the height of the virtual iFrame, all content, not just visible content.<br> 615 * <code>heights.pageHeight</code>: the height of the visible iFrame in the browser.<br> 616 * <code>heights.scrollTop</code>: the position of the scroll bar measured from the top.<br> 617 * <code>widths.contentWidth</code>: the width of the virtual iFrame, all content, not just visible content.<br> 618 * <code>widths.pageWidth</code>: the width of the visible iFrame in the browser.<br> 619 * <code>widths.scrollLeft</code>: the position of the scroll bar measured from the left. 620 * @example 621 * //get the size of the iFrame and print out each component. 622 * var sizes = Sfdc.canvas.client.size(); 623 * console.log("contentHeight; " + sizes.heights.contentHeight); 624 * console.log("pageHeight; " + sizes.heights.pageHeight); 625 * console.log("scrollTop; " + sizes.heights.scrollTop); 626 * console.log("contentWidth; " + sizes.widths.contentWidth); 627 * console.log("pageWidth; " + sizes.widths.pageWidth); 628 * console.log("scrollLeft; " + sizes.widths.scrollLeft); 629 */ 630 size : function() { 631 var docElement = $$.document().documentElement; 632 var contentHeight = docElement.scrollHeight, 633 pageHeight = docElement.clientHeight, 634 scrollTop = (docElement && docElement.scrollTop) || $$.document().body.scrollTop, 635 contentWidth = docElement.scrollWidth, 636 pageWidth = docElement.clientWidth, 637 scrollLeft = (docElement && docElement.scrollLeft) || $$.document().body.scrollLeft; 638 639 return {heights : {contentHeight : contentHeight, pageHeight : pageHeight, scrollTop : scrollTop}, 640 widths : {contentWidth : contentWidth, pageWidth : pageWidth, scrollLeft : scrollLeft}}; 641 }, 642 /** 643 * @public 644 * @name Sfdc.canvas.client#resize 645 * @function 646 * @description Informs the parent window to resize the canvas iFrame. If no parameters are specified, 647 * the parent window attempts to determine the height of the canvas app based on the 648 * content and then sets the iFrame width and height accordingly. To explicitly set the dimensions, 649 * pass in an object with height and/or width properties. 650 * @param {Client} client The object from the signed request 651 * @param {size} size The optional height and width information 652 * @example 653 * //Automatically determine the size 654 * Sfdc.canvas(function() { 655 * sr = JSON.parse('<%=signedRequestJson%>'); 656 * Sfdc.canvas.client.resize(sr.client); 657 * }); 658 * 659 * @example 660 * //Set the height and width explicitly 661 * Sfdc.canvas(function() { 662 * sr = JSON.parse('<%=signedRequestJson%>'); 663 * Sfdc.canvas.client.resize(sr.client, {height : "1000px", width : "900px"}); 664 * }); 665 * 666 * @example 667 * //Set only the height 668 * Sfdc.canvas(function() { 669 * sr = JSON.parse('<%=signedRequestJson%>'); 670 * Sfdc.canvas.client.resize(sr.client, {height : "1000px"}); 671 * }); 672 * 673 */ 674 resize : function(client, size) { 675 var sh, ch, sw, cw, s = {height : "", width : ""}, 676 docElement = $$.document().documentElement; 677 678 // If the size was not supplied, adjust window 679 if ($$.isNil(size)) { 680 sh = docElement.scrollHeight; 681 ch = docElement.clientHeight; 682 if (ch !== sh) { 683 s.height = sh + "px"; 684 } 685 sw = docElement.scrollWidth; 686 cw = docElement.clientWidth; 687 if (sw !== cw) { 688 s.width = sw + "px"; 689 } 690 } 691 else { 692 if (!$$.isNil(size.height)) { 693 s.height = size.height; 694 } 695 if (!$$.isNil(size.width)) { 696 s.width = size.width; 697 } 698 } 699 if (!$$.isNil(s.height) || !$$.isNil(s.width)) { 700 postit(null, {type : "resize", config : {client : client}, size : s}); 701 } 702 }, 703 /** 704 * @public 705 * @name Sfdc.canvas.client#autogrow 706 * @function 707 * @description Starts or stops a timer which checks the content size of the iFrame and 708 * adjusts the frame accordingly. 709 * Use this function when you know your content is changing size, but you're not sure when. There's a delay as 710 * the resizing is done asynchronously. Therfore, if you know when your content changes size, you should 711 * explicitly call the resize() method and save browser CPU cycles. 712 * Note: you should turn off scrolling before this call, otherwise you might get a flicker. 713 * @param {client} client The object from the signed request 714 * @param {boolean} b Whether it's turned on or off; defaults to <code>true</code> 715 * @param {Integer} interval The interval used to check content size; default timeout is 300ms. 716 * @example 717 * 718 * // Turn on auto grow with default settings. 719 * Sfdc.canvas(function() { 720 * sr = JSON.parse('<%=signedRequestJson%>'); 721 * Sfdc.canvas.client.autogrow(sr.client); 722 * }); 723 * 724 * // Turn on auto grow with a polling interval of 100ms (milliseconds). 725 * Sfdc.canvas(function() { 726 * sr = JSON.parse('<%=signedRequestJson%>'); 727 * Sfdc.canvas.client.autogrow(sr.client, true, 100); 728 * }); 729 * 730 * // Turn off auto grow. 731 * Sfdc.canvas(function() { 732 * sr = JSON.parse('<%=signedRequestJson%>'); 733 * Sfdc.canvas.client.autogrow(sr.client, false); 734 * }); 735 */ 736 autogrow : function(client, b, interval) { 737 var ival = ($$.isNil(interval)) ? 300 : interval; 738 autog = ($$.isNil(b)) ? true : b; 739 if (autog === false) { 740 return; 741 } 742 setTimeout(function () { 743 submodules.frame.resize(client); 744 submodules.frame.autogrow(client, autog); 745 },ival); 746 } 747 }; 748 }()); 749 750 return { 751 services : services, 752 frame : frame, 753 event : event, 754 callback : callback 755 }; 756 }()); 757 758 $$.xd.receive(xdCallback, getTargetOrigin); 759 760 return { 761 ctx : submodules.services.ctx, 762 ajax : submodules.services.ajax, 763 token : submodules.services.token, 764 version : submodules.services.version, 765 resize : submodules.frame.resize, 766 size : submodules.frame.size, 767 autogrow : submodules.frame.autogrow, 768 subscribe : submodules.event.subscribe, 769 unsubscribe : submodules.event.unsubscribe, 770 publish : submodules.event.publish, 771 signedrequest : submodules.services.signedrequest, 772 refreshSignedRequest : submodules.services.refreshSignedRequest, 773 repost : submodules.services.repost 774 }; 775 }()); 776 777 $$.module('Sfdc.canvas.client', module); 778 779 }(Sfdc.canvas));