Create your own awesome maps

Even on the go

with our free apps for iPhone, iPad and Android

Get Started

Already have an account?
Log In

Integrating external apps with salesforce using Canvas by Mind Map: Integrating external apps with
salesforce using Canvas
5.0 stars - 1 reviews range from 0 to 5

Integrating external apps with salesforce using Canvas

Any web app (under HTTPS) can be embedded using Canvas

Uses JS SDK to talk with SFDC

Managed as Connected App

Unlike iFrame, Canvas works well with CORS

Can be a Chatter Tab of VisualForce page/element (can be a draggable part of a page)

User isn't aware that it's an external app, as long as its a Signed Request & not OAuth

In the future you'll be able to embed them everywhere (e.g., inside chatter feed)

JS SDK provides everything required for the plumbing & common functions

Integration can also be using SAML

If you're a SAML provider, you can use salesforce Identity

Signed Request POST

Sent when user clicks the canvas app

App needs to verify it arrived from SFDC

using the consumer secret

JSON contents contains

Context, application, user, environment, organization, links, for making API REST calls

...

CanvasRequest object

JS SDK

$$

canvas SDK alias

$$.client.ajax

to make AJAX calls to the REST API

Examples

Query list of accounts

salesforceREST.accountLookup = function(sr, callback) { //TODO #1 Provide the URL to perform a REST query var url = sr.context.links.queryUrl + "?q=SELECT+id,+name+FROM Account"; $$.client.ajax(url, {client : sr.client, //TODO #2 Provide the HTTP method needed for a REST query method: "GET", contentType: "application/json", success : function(data) { if ($$.isFunction(callback)) { //TODO #3 Assign returnedAccounts the value of the JSON array containing the // records returned. var returnedAccounts = data.payload.records; var optionStr = ""; for (var acctPos = 0; acctPos < returnedAccounts.length; acctPos = acctPos + 1){ optionStr = optionStr + '<option value="' //TODO #4 Replace the "" with a reference to the Id of the account in the current position. + returnedAccounts[acctPos].Id + '">' //TODO #5 Replace the "" with a reference to the Name of the account in the current position. + returnedAccounts[acctPos].Name + '</option>'; } callback(optionStr); } }, error: function() { alert("I'm sorry we can't populate the menu at this time. Please contact your system administrator if the problem persists."); } }); };

Add contact

/** * POST a request to salesforce using the REST API. Return the result. */ salesforceREST.contactAdd = function(sr, firstNameInput, lastNameInput, accountIdInput, callback) { //TODO #6 Provide the URL to perform a REST insert. var url = sr.context.links.sobjectUrl + "Contact/"; //TODO #7 Assign the body a list of JSON key-value pairs that include the firstName, lastName, // and accountId fields. var body = { firstName: firstNameInput, lastName: lastNameInput, accountId: accountIdInput }; $$.client.ajax(url, {client : sr.client, //TODO #8 Provide the HTTP method needed for a REST query method: "POST", contentType: "application/json", data: JSON.stringify(body), success : function(data) { if ($$.isFunction(callback)) { callback(data); } }, error: function() { alert("I'm sorry we can't handle your request at this time. Please contact your system administrator if the problem persists."); } }); };

Full example

Template & JS, <%-- Copyright (c) 2011, salesforce.com, inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --%> <%@ page import="canvas.SignedRequest" %> <%@ page import="java.util.Map" %> <% // Pull the signed request out of the request body and verify/decode it. Map<String, String[]> parameters = request.getParameterMap(); String[] signedRequest = parameters.get("signed_request"); if (signedRequest == null) {%> This App must be invoked via a signed request!<% return; } //In a production level application, you would not store your consumer secret directly //in the code. You might have something like: String yourConsumerSecret=System.getenv("CANVAS_CONSUMER_SECRET"); //TODO #1 Add your consumer secret. String yourConsumerSecret="8774399243183795538"; String signedRequestJson = SignedRequest.verifyAndDecodeAsJson(signedRequest[0], yourConsumerSecret); System.out.println("SignedRequest is: \n" + signedRequestJson); %> <!DOCTYPE html> <html> <head> <title>Force.com Canvas Workshop - Dreamforce '13</title> <link rel="stylesheet" type="text/css" href="/sdk/css/canvas.css" /> <script type="text/javascript" src="/sdk/js/canvas-all.js"></script> <script type="text/javascript" src="/scripts/json2.js"></script> <!-- For testing purposes, we're adding a parameter here to force the browser to refresh the javascript file, as it changes during the exercises. --> <script type="text/javascript" src="/scripts/salesforceREST.js?<%= java.lang.Math.random() %>"></script> <script> if (self === top) { // Not in Iframe alert("This canvas app must be included within an iframe"); }; Sfdc.canvas(function() { var sr = JSON.parse('<%=signedRequestJson%>'); var photoUri = sr.context.user.profileThumbnailUrl + "?oauth_token=" + sr.client.oauthToken; /** * Check if we are in sites/communities. If so, derive the url accordingly. */ var isSites=null != sr.context.user.networkId; var siteHost = isSites ? sr.context.user.siteUrl : sr.client.instanceUrl; if (siteHost.lastIndexOf("/") == siteHost.length-1){ siteHost = siteHost.substring(0,siteHost.length-1); }; /** * Using the context provided by the request from Salesforce, populate the top elements of the form. */ Sfdc.canvas.byId("fullname").innerHTML = sr.context.user.fullName; Sfdc.canvas.byId("firstname").innerHTML = sr.context.user.firstName; Sfdc.canvas.byId("lastname").innerHTML = sr.context.user.lastName; Sfdc.canvas.byId("profile").src = (photoUri.indexOf("http")==0 ? "" :siteHost) + photoUri; //TODO #2: Define the username, email, and company variables using Javascript. // (You will replace the double quotation marks.) Sfdc.canvas.byId("username").innerHTML = sr.context.user.userName; Sfdc.canvas.byId("email").innerHTML = sr.context.user.email; Sfdc.canvas.byId("company").innerHTML = sr.context.organization.name; /** * Using the information returned in the context, we will request the current Salesforce * account list for display in the data entry form. */ salesforceREST.accountLookup(sr, function (data) { Sfdc.canvas.byId("sfdc-account-list").innerHTML = Sfdc.canvas.byId("sfdc-account-list").innerHTML + data; }); /** * If the submission includes an account, then we will POST a request to salesforce using the REST API * to add the contact. Otherwise, we will request that the user select an account. */ contactTransfer = function() { var fname = Sfdc.canvas.byId("firstName-input-field").value; var lname = Sfdc.canvas.byId("lastName-input-field").value; var acctId = Sfdc.canvas.byId("sfdc-account-list").value; if (acctId =="NULL") { alert("Please select an account before submitting your contact."); } else { salesforceREST.contactAdd(sr, fname, lname, acctId, function(data) { if (data.statusText =="Bad Request") { var badData = data.payload; var errorMsg = ""; for (var errorPos = 0; errorPos < badData.length; errorPos = errorPos + 1){ errorMsg = errorMsg + badData[errorPos].message + "\n"; } Sfdc.canvas.byId("status").innerHTML = errorMsg; } else { Sfdc.canvas.byId("status").innerHTML = data.statusText; Sfdc.canvas.byId("firstName-input-field").value = ""; Sfdc.canvas.byId("lastName-input-field").value = ""; Sfdc.canvas.byId("sfdc-account-list")[0].selected = true; } }); } }; }); </script> </head> <body> <div id="page"> <div id="content"> <div id="header"> <h1 >Hello <span id="fullname"></span>!</h1> <h2>Welcome to Galaxy World Movers Contact Application</h2> </div> <div id="canvas-content"> <h1>Canvas Request</h1> <h2>Below is some information received in the Canvas Request:</h2> <div id="canvas-request"> <table border="0" width="100%"> <tr> <td></td> <td><b>First Name: </b><span id="firstname"></span></td> <td><b>Last Name: </b><span id="lastname"></span></td> </tr> <tr> <td><img id="profile" border="0" src="" /></td> <td><b>Username: </b><span id="username"></span></td> <td colspan="2"><b>Email Address: </b><span id="email"></span></td> </tr> <tr> <td></td> <td colspan="3"><b>Company: </b><span id="company"></span></td> </tr> </table> </div> <div id="canvas-contactEntry"> <h1>Galaxy World Movers Contact Information</h1> <table border="0" width="100%"> <tr> <td width="15%"><b>Salesforce Account:&nbsp</b></td> <td colspan="4"> <select name="sfdc-account-list" id="sfdc-account-list" > <option value="NULL">Please select an account:</option> </select> </td> </tr> <tr> <td width="15%"><b>New Contact:&nbsp</b></td> <td width="35%">First Name: <input id="firstName-input-field" type="text" x-webkit-speech/></td> <td width="35%">Last Name: <input id="lastName-input-field" type="text" x-webkit-speech/></td> <td width="8%"><button onclick="contactTransfer()" id="contact-submit" type="submit" >Submit</button></td> <td width="8%"><strong><span id="status" style="color:green"></strong></span></td> </tr> </table> </div> </div> </div> <div id="footercont"> <div id="footerleft"> <p>Powered By: <a title="Heroku" href="#" onclick="window.top.location.href='http://www.heroku.com'"><strong>Heroku</strong></a></p> </div> <div id="footerright"> <p>Salesforce: <a title="Safe Harbor" href="http://www.salesforce.com/company/investor/safe_harbor.jsp"><strong>SafeHarbor</strong></a></p> </div> </div> </div> </body> </html>

JS, // Copyright (c) 2011, salesforce.com, inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted provided // that the following conditions are met: // // Redistributions of source code must retain the above copyright notice, this list of conditions and the // following disclaimer. // // Redistributions in binary form must reproduce the above copyright notice, this list of conditions and // the following disclaimer in the documentation and/or other materials provided with the distribution. // // Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or // promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. var salesforceREST; if (!salesforceREST) { salesforceREST = {}; } (function ($$) { "use strict"; salesforceREST.accountLookup = function(sr, callback) { //TODO #1 Provide the URL to perform a REST query var url = sr.context.links.queryUrl + "?q=SELECT+id,+name+FROM Account"; $$.client.ajax(url, {client : sr.client, //TODO #2 Provide the HTTP method needed for a REST query method: "GET", contentType: "application/json", success : function(data) { if ($$.isFunction(callback)) { //TODO #3 Assign returnedAccounts the value of the JSON array containing the // records returned. var returnedAccounts = data.payload.records; var optionStr = ""; for (var acctPos = 0; acctPos < returnedAccounts.length; acctPos = acctPos + 1){ optionStr = optionStr + '<option value="' //TODO #4 Replace the "" with a reference to the Id of the account in the current position. + returnedAccounts[acctPos].Id + '">' //TODO #5 Replace the "" with a reference to the Name of the account in the current position. + returnedAccounts[acctPos].Name + '</option>'; } callback(optionStr); } }, error: function() { alert("I'm sorry we can't populate the menu at this time. Please contact your system administrator if the problem persists."); } }); }; /** * POST a request to salesforce using the REST API. Return the result. */ salesforceREST.contactAdd = function(sr, firstNameInput, lastNameInput, accountIdInput, callback) { //TODO #6 Provide the URL to perform a REST insert. var url = sr.context.links.sobjectUrl + "Contact/"; //TODO #7 Assign the body a list of JSON key-value pairs that include the firstName, lastName, // and accountId fields. var body = { firstName: firstNameInput, lastName: lastNameInput, accountId: accountIdInput }; $$.client.ajax(url, {client : sr.client, //TODO #8 Provide the HTTP method needed for a REST query method: "POST", contentType: "application/json", data: JSON.stringify(body), success : function(data) { if ($$.isFunction(callback)) { callback(data); } }, error: function() { alert("I'm sorry we can't handle your request at this time. Please contact your system administrator if the problem persists."); } }); }; }(Sfdc.canvas));