├── .gitignore ├── LICENSE ├── README.md ├── app ├── canvas-controller.js ├── canvas-directives.js ├── canvas-services.js ├── canvas.js └── examples │ ├── edit │ ├── edit-controller.js │ └── edit.html │ ├── events.html │ ├── lightning.html │ ├── oauth │ ├── oauth-controller.js │ └── oauth.html │ ├── overview.html │ ├── queryA │ ├── queryA-controller.js │ └── queryA.html │ ├── queryB │ ├── queryB-controller.js │ └── queryB.html │ ├── resizing.html │ └── signed-request.html ├── css ├── canvas-starter-styles.css └── salesforce-lightning-design-system.css ├── df16_slides ├── AnyExistingWebApp.key └── AnyExistingWebApp.pptx ├── fonts ├── License-for-font.txt ├── SalesforceSans-Bold.ttf ├── SalesforceSans-BoldItalic.ttf ├── SalesforceSans-Italic.ttf ├── SalesforceSans-Light.ttf ├── SalesforceSans-LightItalic.ttf ├── SalesforceSans-Regular.ttf ├── SalesforceSans-Thin.ttf ├── SalesforceSans-ThinItalic.ttf └── webfonts │ ├── SalesforceSans-Bold.eot │ ├── SalesforceSans-Bold.svg │ ├── SalesforceSans-Bold.woff │ ├── SalesforceSans-Bold.woff2 │ ├── SalesforceSans-BoldItalic.eot │ ├── SalesforceSans-BoldItalic.svg │ ├── SalesforceSans-BoldItalic.woff │ ├── SalesforceSans-BoldItalic.woff2 │ ├── SalesforceSans-Italic.eot │ ├── SalesforceSans-Italic.svg │ ├── SalesforceSans-Italic.woff │ ├── SalesforceSans-Italic.woff2 │ ├── SalesforceSans-Light.eot │ ├── SalesforceSans-Light.svg │ ├── SalesforceSans-Light.woff │ ├── SalesforceSans-Light.woff2 │ ├── SalesforceSans-LightItalic.eot │ ├── SalesforceSans-LightItalic.svg │ ├── SalesforceSans-LightItalic.woff │ ├── SalesforceSans-LightItalic.woff2 │ ├── SalesforceSans-Regular.eot │ ├── SalesforceSans-Regular.svg │ ├── SalesforceSans-Regular.woff │ ├── SalesforceSans-Regular.woff2 │ ├── SalesforceSans-Thin.eot │ ├── SalesforceSans-Thin.svg │ ├── SalesforceSans-Thin.woff │ ├── SalesforceSans-Thin.woff2 │ ├── SalesforceSans-ThinItalic.eot │ ├── SalesforceSans-ThinItalic.svg │ ├── SalesforceSans-ThinItalic.woff │ └── SalesforceSans-ThinItalic.woff2 ├── img └── VF.png ├── index.html ├── libraries ├── angular.js ├── canvas-all.js ├── canvas-starter.js └── ng-route.js ├── oauth ├── callback.html └── sfOauth.html ├── packageComponents ├── Canvas_Starter.vf ├── apexClasses │ ├── AlternateResource.apex │ └── AlternateResourceTest.apex └── staticresources │ ├── canvas-static.js │ └── styles │ ├── lightning.css │ └── styles.css └── php ├── canvas.php └── consumerData.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # Vagrant # 30 | ###################### 31 | vagrant 32 | connect 33 | 34 | # PHP To not accidentally push the keys # 35 | ###################### 36 | php 37 | 38 | # OS generated files # 39 | ###################### 40 | .DS_Store 41 | .DS_Store? 42 | ._* 43 | .Spotlight-V100 44 | .Trashes 45 | ehthumbs.db 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SeedCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Canvas Starter Kit 2 | This kit is designed for JavaScript developers who want to build a Salesforce app using the Force.com Canvas Framework. It can serve as starting point or template for building your own Canvas app and publishing it as a Managed Package on the AppExchange. 3 | 4 | ### We've been there! 5 | This kit is the result of our journey bringing our [DayBack Web App](https://appexchange.salesforce.com/listingDetail?listingId=a0N30000000qp64EAA) to the Salesforce AppExchange. We learned a lot during this experience, some easy things, some harder things, and wanted to share this with people who are where we were. We also wanted to provide a simpler "Hello World" Experience for prospective Canvas users. The Java example provided by Salesforce is very good, but a little complicated for JavaScript folks (like us) who are maybe a little more comfortable with a simple PHP page than a full Java web app. 6 | 7 | ### Help us out! 8 | We are very pleased with how we incorporated our app using the techniques in this kit, but we're sure there are better methods we missed (or mis-understood), so please contribute to improving this kit. 9 | 10 | ### What's in the kit? 11 | ##### Package Components: 12 | - Visualforce Code for creating a Canvas app in a VF page 13 | - [Static Resource](https://developer.salesforce.com/docs/atlas.en-us.pages.meta/pages/pages_resources.htm) Javascript that provides: 14 | - Resizing your Canvas app within the tab 15 | - Navigation in Salesforce from the Canvas app 16 | - A template for publishing and subscribing to events between the VF page and the Canvas app 17 | - Functionality across VF, Lightning and SF1 18 | - Static Resource CSS for optimizing the Visualforce page in the tab for VF, Lightning and SF1. 19 | - Apex Classes that allow an org to use its own Static Resource in an installed Managed Package. 20 | 21 | ##### App Components: 22 | - [The Salesforce Canvas Javascript SDK](https://github.com/forcedotcom/SalesforceCanvasJavascriptSDK) 23 | - A basic PHP page for authenticating your Canvas app with Salesforce 24 | - A basic OAuth.html page for when users must self-authorize 25 | - A basic callback.html page for OAuth handling 26 | - Simplified Javascript functions for handling 27 | - OAuth 28 | - CRUD with Salesforce data 29 | - Publishing and subscribing to events between the VF page and the Canvas app 30 | - A sample Angular app with simple examples of the functionality for re-engineering 31 | 32 | ##### Managed Package: 33 | We put together a Managed Package of the Sample Angular app with all of the above components so you can see how it works once completed. We wanted to provide this as an Unmanaged Package, but Connected/Canvas apps are not permitted in Unmanaged Packages. The Managed Package Components are all in this repository and you'll find step-by-step instructions, below for re-building the App that you can turn into your own Managed Package. Here are the install links for the Managed Package: 34 | 35 | - [Production Install](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t36000000xjk4) 36 | - [Sandbox Install](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t36000000xjk4) 37 | 38 | ### Building the Kit in Your Org 39 | 40 | ##### A Hosted App 41 | The idea behind Canvas is that you have an existing Web App you'd like to bring into Salesforce, so presumably your app is currently already hosted. Salesforce does require **https** for your app to work in Canvas. You can get around this a little bit in development by making a browser exception to the PHP endpoint, but not with the OAuth callback endpoint. 42 | If you are starting from the very beginning, then [Heroku](https://www.heroku.com/) and [Appfog](https://www.ctl.io/appfog/) are excellent services for hosting your app, and Salesforce provides a [Heroku Quickstart](https://developer.salesforce.com/docs/atlas.en-us.salesforce1api.meta/salesforce1api/heroku_quick_start.htm) path for developers. 43 | 44 | The only server technology this kit requires is PHP. Once your hosting is set up, deploy the whole kit, except for the packageComponents folder. You can now set up the components in your Salesforce Developer Org. 45 | 46 | ##### Adding the Package components 47 | You will need to do this in a Salesforce Development Org. Only Development orgs can create Canvas apps and Managed Packages. We recommend going ahead and setting up a [namespace prefix](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_namespace_prefix.htm) for your org now, if you don't already have one. You can't do Unmanaged Packages with Canvas, and you need the prefix for the managed one. 48 | 49 | Please follow the following steps in order to re-create the kit app in your org. 50 | 51 | 1. **Set Up the Canvas App** 52 | 1. Go To SetUp / Create / apps 53 | 2. Scroll Down to Connected Apps and click New 54 | 3. Name your App and fill in the other required fields in the top section 55 | 4. Enable OAuth Settings 56 | 1. For the Callback URL specify **https://oauth/callback.html** 57 | 2. Enable OAuth Scopes. If you're not sure what to do here, we recommend starting with **Access and manage your data** and **Access your basic information** 58 | 5. Skip down to the Canvas App Settings and check Force.com Canvas 59 | 1. For the Canvas App URL, enter **https://php/canvas.php** 60 | 2. For Access Method, choose **Signed Request (POST)** 61 | 3. For Locations, choose (at least) **Visualforce Page** 62 | 6. Click Save 63 | 7. You will now be brought to the Detail Page. From here please copy the following three values. We need to add these to our PHP pages to allow the handshaking between our app and Canvas: 64 | 1. **Consumer Key** 65 | 2. **Consumer Secret** (You need to click to reveal it) 66 | 3. **Callback URL** 67 | 68 | 2. **Add App Consumer Data to PHP Pages** 69 | 1. In the Hosted app find the [canvas.php](https://github.com/seedcode/canvas-starter-kit/blob/master/php/canvas.php) and the [consumerData.php](https://github.com/seedcode/canvas-starter-kit/blob/master/php/consumerData.php) pages in the app's php folder. 70 | 2. In the canvas.php page enter the consumer secret on line 10 in the spot reserved by _<consumer secret for your connected/canvas app>_. 71 | 3. In the consumerData.php file enter your consumer key on line 2 in the spot reserved by _<consumer key for your connected/canvas app>_. 72 | 4. In the consumerData.php file enter your callback URL on line 3 in the spot resrerved by _<callback url for your connected/canvas app>_. 73 | 74 | 3. **Create the Custom Setting** 75 | 1. Go To SetUp/Develop/Custom Settings 76 | 2. Click New 77 | 3. Name the Custom Setting Definition **alternateJavascript** 78 | 4. Setting Type should be **Hierarchy** 79 | 5. Visibility should be **public** 80 | 6. Description should be "The name of an alternate javascript file for use by the canvas visualforce page." 81 | 7. Click Save 82 | 8. Now click New in the Custom Fields section 83 | 9. Name the Field Resource Name 84 | 10. Set the Type to Text and give it a length of 100 85 | 11. Click Save 86 | 87 | 4. **Upload the Static Resources** 88 | 1. Go To SetUp/Develop/Static Resources 89 | 2. Click New 90 | 3. Name the Static Resource **canvasStatic** and upload the [canvas-static.js](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/staticresources/canvas-static.js) file from the [packageComponents/staticrecources/](https://github.com/seedcode/canvas-starter-kit/tree/master/packageComponents/staticresources) folder 91 | 4. Find the [styles](https://github.com/seedcode/canvas-starter-kit/tree/master/packageComponents/staticresources/styles) folder in the same [packageComponents/staticrecources/](https://github.com/seedcode/canvas-starter-kit/tree/master/packageComponents/staticresources) folder and zip it 92 | 5. Once the styles folder is zipped create a new static resource named **style_reources** and upload the zipped folder to this resource. 93 | 94 | 5. **Create the Apex Classes** 95 | 1. Go To SetUp/Develop/Apex Classes 96 | 2. Click New 97 | 3. Paste in the contents of the [AlternateResource.apex](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/apexClasses/AlternateResource.apex) file 98 | 4. Click Save 99 | 5. Managed Packages require [75% Testing Coverage](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_testing_best_practices.htm), so repeat the above steps for the [AlternateResourceTest.apex](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/apexClasses/AlternateResourceTest.apex) file for this coverage. 100 | 6. You can then click Run Test from the Test Class. (Hopefully it passes!) 101 | 7. If you've set up your development org prefix, then add it to the appropriate spot in both classes. 102 | 1. In [AlternateResource.apex](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/apexClasses/AlternateResource.apex), change lines 7 and 12 to pre='' 103 | 2. In [AlternateResourceTest.apex](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/apexClasses/AlternateResourceTest.apex) do the same for lines 13 and 23 104 | 8. Run the test again (just to be sure!) 105 | 106 | 6. **Create the Visualforce page** 107 | 1. Go To SetUp/Develop/Visualforce Pages 108 | 2. Click New 109 | 3. Name the page **Canvas Starter** 110 | 4. Click the checkbox for **Available for Salesforce mobile apps and Lightning Pages** 111 | 5. In the code section paste in the contents of our [Canvas_Starter.vf](https://github.com/seedcode/canvas-starter-kit/blob/master/packageComponents/Canvas_Starter.vf) page. 112 | 6. Click Save 113 | 7. In the Visualforce Page List View, click on Security and enable the Profiles that have access to this page. 114 | 115 | 7. **Create the Visualforce Tab** 116 | 1. Go To SetUp/Create/Tabs 117 | 2. Click New in the Visualforce Tab section 118 | 3. Name the Tab **Canvas Starter** 119 | 4. Select a Tab style 120 | 5. Click Save 121 | 122 | ##### You should now be able to access the app as a Visualforce tab in Visualforce or Lightning! 123 | 124 | [![Hello](img/VF.png)](img/VF.png) 125 | 126 | ### JavaScript Function Reference 127 | 128 | This kit includes the [Salesforce Canvas Javascript SDK](https://github.com/forcedotcom/SalesforceCanvasJavascriptSDK), and you can reference the directly. The canvas sdk function reference is [here](https://htmlpreview.github.io/?https://raw.githubusercontent.com/forcedotcom/SalesforceCanvasJavascriptSDK/master/docs/symbols/Sfdc.canvas.html 129 | ). Additionally the [canvas-starter.js](https://github.com/seedcode/canvas-starter-kit/blob/master/libraries/canvas-starter.js) file loads the **cnv** object, based on the sdk, with the following public methods: 130 | 131 | ##### initialze ( [ callback ] ) 132 | 133 | This function retrieves the signed request after the user has authenticated into salesforce and stores it for subsequent calls in the cnv object. It also publishes a resize event to the visualforce page. In the canvas-starter-kit example, this function is called by an angular directive when the app loads. The callback function is optional and returns the signed request object. 134 | - **callback** (optional) _function_: function for handling the signed request. 135 | 136 | example: 137 | ```javascript 138 | function sayHi(signedrequest) { 139 | document.getElementById('firstName') = signedRequest.client.user.firstName; 140 | } 141 | cnv.initialize (sayHi); 142 | ``` 143 | 144 | ##### login () 145 | 146 | Initiates the login pop-up from Salesforce to authenticate the canvas app (when required) . This function requires the **consumer key**, the **consumer secret** and the **callback html** to be set in the php files per step 2 above. 147 | 148 | example: 149 | ```html 150 | 151 | 152 | ``` 153 | 154 | ##### logout ( [ loginPage ] ) 155 | 156 | Deletes the Access token from the canvas object and the one sored in the cnv object . This function requires the **consumer key**, the **consumer secret** and the **callback html** to be set in the php files per step 2 above. 157 | - **loginPage** (optional) _boolean_: redirects to the OAuth page (/oauth.sfOauth.html) after the access token is cleared. 158 | 159 | example: 160 | ```html 161 | 162 | ``` 163 | 164 | ##### refresh () 165 | 166 | Used as the callback function in the OAuth callback url page(/oauth/callback.html). The function simply navigates back to the index page of the app re-initializing it after log-in. 167 | 168 | example: 169 | ```javascript 170 | //run from the OAuth popover callback.html page 171 | //notify parent window we're authorized 172 | try { 173 | if(window.opener.cnv) { 174 | window.opener.cnv.refresh(); 175 | } 176 | else { 177 | //cnv should be there, but use the standard canvas function as a fallback 178 | window.opener.Sfdc.canvas.oauth.childWindowUnloadNotification(self.location.hash); 179 | } 180 | } catch (ignore) {} 181 | self.close(); 182 | ``` 183 | 184 | ##### querySalesforce( query, callback ) 185 | 186 | General AJAX query for getting salesforce data. 187 | - **query** _string_: A SOQL query 188 | - **callback** _function_: the handler for the query result. 189 | 190 | example: 191 | ```javascript 192 | //get leads sorted by most recently viewed by me 193 | var query = 'SELECT Id,Name,LastViewedDate FROM Account ORDER BY LastViewedDate DESC NULLS LAST' 194 | function showResults(result) { 195 | document.getElementById('result').innerHTML = JSON.stringify(result,null,2); 196 | } 197 | cnv.querySalesforce(query, showResults); 198 | ``` 199 | 200 | ##### editSalesforce( object, request, callback ) 201 | 202 | General AJAX query for editing/creating salesforce data. 203 | - **object** _string_: The target Salesforce Object 204 | - **request** _object_: A JavaScript object for the edit request. If the Id property is not included in the request, then a new record will be created in the target vie **POST**. If the Id is specified in the request, then a **PATCH** will be sent to the target record with the specified changes. 205 | - **callback** _function_: the handler for the query result. 206 | 207 | example: 208 | ```javascript 209 | //create a test task for tomorrow 210 | var due = new Date(); 211 | due.setDate(due.getDate()+1); 212 | due = due.toISOString(); 213 | var request = { 214 | 'ActivityDate':due.substring(0,10), 215 | 'Subject':'Test Task From the canvas-starter-kit', 216 | }; 217 | cnv.editSalesforce('Task',request,process); 218 | function process(result) { 219 | document.getElementById.innerHTML = 'new task result: ' + JSON.stingify(result,null,2); 220 | } 221 | 222 | ``` 223 | 224 | ##### deleteSalesforce( object, id, callback ) 225 | 226 | General AJAX query for deleting salesforce data. 227 | - **object** _string_: The target Salesforce Object 228 | - **id** _string_: The target record's Id. 229 | - **callback** _function_: the handler for the query result. 230 | 231 | example: 232 | ```javascript 233 | cnv.deleteSalesforce('Task',this.id,process); 234 | function process(result) { 235 | if(result && result[0].errorCode) { 236 | //error deleting task 237 | alert(result[0].errorCode) 238 | } 239 | else{ 240 | //task deleted, remove this element. 241 | var element = document.getElementById(this.id); 242 | element.parentNode.removeChild(element); 243 | } 244 | } 245 | ``` 246 | 247 | ##### publish( event [, payload ] ) 248 | 249 | General function for publishing an event to a visualforce page. The visualforce page must be subscribing to this event to take action, 250 | - **event** _string_: The name of the subscription event we want to trigger. Typically has a prefix matching the namespace, although this is not a requirement. 251 | - **payload** (optional) _object_: The data object to be processed by the subscribing event,. 252 | 253 | example: 254 | ```javascript 255 | //publish an event to the cnvstart.navigate subscription to go to 256 | //this record (a new tab/window if not in lightning or sf1) 257 | publish ( "cnvstart.navigate" , { 258 | 'id':this.id, 259 | 'new' : true } ); 260 | } 261 | ``` 262 | 263 | ##### navigate( id, url [, newWindow] ) 264 | 265 | Formatted function for publishing to the cnvstart.navigate subscription and navigating in the parent window. 266 | - **id** _string_: The id of the target Salesforce Object. If the id is specified, then the url property is ignored. If in Visualforce and the newWindow property is set to true, then the target object will be opened in a new tab/window. If in lightning or SF1 the newWindow property is ignored. 267 | - **url** _string_: The url to navigate to in the outer window. When specifying the url property, then pass _null_ for the id property. 268 | - ** newWindow** (optional, default:false) _boolean_: If **url** is specified then the function will attempt to open the target in a new window/tab when newWindow is set to true. If in Visualforce and **id** is specified then the function will attempt to open the target object in a new tab/window. If in Lightning or SF1 then this propert is ignored. 269 | 270 | example: 271 | ```javascript 272 | cnv.navigate(this.id, null, true); 273 | ``` 274 | -------------------------------------------------------------------------------- /app/canvas-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('AppCtrl', ['$scope','$location','canvas', AppCtrl]); 7 | 8 | function AppCtrl($scope,$location,canvas) { 9 | 10 | $scope.firstName=''; 11 | 12 | $scope.chapters=canvas.chapters(); 13 | 14 | $scope.selected= 'Overview'; 15 | 16 | $scope.updateSelected = updateSelected; 17 | 18 | function updateSelected(selected,hash) { 19 | $scope.selected = selected; 20 | $scope.result = ''; 21 | $scope.newId = ''; 22 | $scope.success='false'; 23 | var url = $location.url(hash); 24 | } 25 | 26 | 27 | } 28 | 29 | }()); 30 | -------------------------------------------------------------------------------- /app/canvas-directives.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .directive('initialize', ['$window', '$rootScope', '$timeout','canvas', initialize]) 7 | .directive('sizeContent', ['$window', '$rootScope', '$timeout','canvas', sizeContent]); 8 | 9 | function initialize($window, $rootScope, $timeout, canvas) { 10 | return function(scope, element, attributes) { 11 | var resize; 12 | 13 | //initialize our canvas library, this will resize the canvas app and force a resize 14 | canvas.initialize(confirmInitialization); 15 | 16 | function confirmInitialization(result) { 17 | scope.$evalAsync(function(){ 18 | scope.firstName = result.context.user.firstName; 19 | scope.context = JSON.stringify(result.context, null, 2); 20 | }); 21 | } 22 | 23 | //On window resize => resize the app 24 | angular.element($window).on('resize', windowResize); 25 | 26 | function windowResize(e) { 27 | //Compare this to event target to make sure this isn't an event that has bubbled up 28 | if (this == e.target) { 29 | $rootScope.$broadcast('resize'); 30 | $timeout.cancel(resize); 31 | resize = $timeout(function() { 32 | resizeBody($window); 33 | }, 200); 34 | } 35 | } 36 | }; 37 | } 38 | 39 | function sizeContent($window, $rootScope, $timeout) { 40 | return function(scope, element, attributes){ 41 | var resize = $timeout(function() { 42 | resizeBody($window); 43 | }, 200); 44 | }; 45 | } 46 | 47 | function resizeBody(window) { 48 | var height = window.innerHeight; 49 | var width = window.innerWidth; 50 | var sideBarWidth = document.getElementById('sidebar').offsetWidth; 51 | document.body.style.height = height + "px"; 52 | document.getElementById('content').style.width = width - sideBarWidth + "px"; 53 | } 54 | 55 | }()); 56 | -------------------------------------------------------------------------------- /app/canvas-services.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .factory('canvas', canvas); 7 | 8 | function canvas($sce) { 9 | 10 | return { 11 | initialize:initialize, 12 | login:cnv.login, 13 | logout:cnv.logout, 14 | querySalesforce:querySalesforce, 15 | editSalesforce:editSalesforce, 16 | deleteSalesforce:deleteSalesforce, 17 | chapters:chapters, 18 | }; 19 | 20 | function querySalesforce(query,callback,recordsOnly) { 21 | if(recordsOnly!==false){ 22 | recordsOnly=true; 23 | } 24 | cnv.querySalesforce(query,process); 25 | function process(result) { 26 | if(result.status===0){ 27 | alert('No Response from Salesforce, Check Internet Connection.'); 28 | return; 29 | } 30 | else if (result.status===200) { 31 | //success 32 | if(recordsOnly){ 33 | callback(result.payload.records); 34 | } 35 | else { 36 | callback(result); 37 | } 38 | } 39 | else if(result.payload && result.payload[0] && result.payload[0].errorCode){ 40 | if(recordsOnly) { 41 | alert(result.payload[0].errorCode); 42 | } 43 | else { 44 | callback(result); 45 | } 46 | } 47 | } 48 | } 49 | 50 | function editSalesforce(object,request,callback) { 51 | cnv.editSalesforce(object,request,process); 52 | function process(result) { 53 | if(result.errors[0]) { 54 | alert(errorCode.errors[0].errorCode); 55 | } 56 | else{ 57 | callback(result.id); 58 | } 59 | } 60 | } 61 | 62 | function deleteSalesforce(object,request,callback) { 63 | cnv.deleteSalesforce(object,request,process); 64 | function process(result) { 65 | if(result && result[0].errorCode) { 66 | alert(result[0].errorCode); 67 | callback(true); 68 | } 69 | else{ 70 | callback(result[0].success); 71 | } 72 | } 73 | } 74 | 75 | function initialize(callback) { 76 | cnv.initialize(checkCanvasLoad); 77 | function checkCanvasLoad(result) { 78 | if(result.errorCode) { 79 | alert(errorCode); 80 | } 81 | else if (callback) { 82 | callback(result); 83 | } 84 | } 85 | } 86 | 87 | function chapters(){ 88 | return [ 89 | { 90 | 'label':'Overview', 91 | 'hash':'Overview', 92 | }, 93 | { 94 | 'label':'The Signed Request', 95 | 'hash':'SignedRequest', 96 | }, 97 | { 98 | 'label':'OAuth', 99 | 'hash':'OAuth', 100 | }, 101 | { 102 | 'label':'Querying Salesforce Data A', 103 | 'hash':'QueryA', 104 | }, 105 | { 106 | 'label':'Querying Salesforce Data B', 107 | 'hash':'QueryB', 108 | }, 109 | { 110 | 'label':'Editing Salesforce Data', 111 | 'hash':'Editing', 112 | }, 113 | { 114 | 'label':'Canvas Events', 115 | 'hash':'Events', 116 | }, 117 | { 118 | 'label':'Resizing', 119 | 'hash':'Resizing', 120 | }, 121 | { 122 | 'label':'Lightning', 123 | 'hash':'Lightning', 124 | }, 125 | 126 | /* Coming soon 127 | { 128 | 'label':'Managed Packages', 129 | 'hash':'Packages', 130 | }, 131 | { 132 | 'label':'Security Review', 133 | 'hash':'Security', 134 | } 135 | */ 136 | 137 | ]; 138 | } 139 | 140 | } 141 | }()); 142 | -------------------------------------------------------------------------------- /app/canvas.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | angular 4 | .module('app',['ngRoute']) 5 | .config(['$routeProvider', router]); 6 | 7 | function router($routeProvider){ 8 | $routeProvider.when('/Overview' , { 9 | templateUrl: 'app/examples/overview.html' 10 | }) 11 | .when('/SignedRequest', { 12 | templateUrl: 'app/examples/signed-request.html' 13 | }) 14 | .when('/OAuth', { 15 | templateUrl: 'app/examples/oauth/oauth.html' 16 | }) 17 | .when('/QueryA', { 18 | templateUrl: 'app/examples/queryA/queryA.html' 19 | }) 20 | .when('/QueryB', { 21 | templateUrl: 'app/examples/queryB/queryB.html' 22 | }) 23 | .when('/Editing', { 24 | templateUrl: 'app/examples/edit/edit.html' 25 | }) 26 | .when('/Events', { 27 | templateUrl: 'app/examples/events.html' 28 | }) 29 | .when('/Resizing', { 30 | templateUrl: 'app/examples/resizing.html' 31 | }) 32 | .when('/Lightning', { 33 | templateUrl: 'app/examples/lightning.html' 34 | }); 35 | } 36 | 37 | })(); 38 | -------------------------------------------------------------------------------- /app/examples/edit/edit-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('EditCtrl', ['$scope','canvas', EditCtrl]); 7 | 8 | function EditCtrl($scope,canvas) { 9 | 10 | $scope.newTask = newTask; 11 | 12 | $scope.deleteTask = deleteTask; 13 | 14 | $scope.newId = ''; 15 | 16 | $scope.success = 'false'; 17 | 18 | $scope.navigate = navigate; 19 | 20 | function newTask(){ 21 | document.getElementById('newTask').blur(); 22 | var due = new Date(); 23 | due.setDate(due.getDate()+1); 24 | due = due.toISOString(); 25 | var request = { 26 | 'ActivityDate':due.substring(0,10), 27 | 'Subject':'Test Task From the canvas-starter-kit', 28 | }; 29 | canvas.editSalesforce('Task',request,process); 30 | function process(result) { 31 | $scope.$evalAsync(function(){ 32 | $scope.newId = result; 33 | $scope.success = true; 34 | }); 35 | } 36 | } 37 | 38 | function deleteTask(id) { 39 | canvas.deleteSalesforce('Task',id,process); 40 | function process(result) { 41 | if(result) { 42 | $scope.$evalAsync(function(){ 43 | $scope.newId = ''; 44 | $scope.success = false; 45 | }); 46 | } 47 | } 48 | } 49 | 50 | function navigate(id) { 51 | cnv.navigate(id,null,true); 52 | } 53 | 54 | } 55 | 56 | }()); 57 | -------------------------------------------------------------------------------- /app/examples/edit/edit.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Editing Salesforce Data

4 |

Once initialized, you can use the following functions to create, edit and delete salesforce records. Refer to the Rest API Guide and the Object Reference for Salesforce for the correct field names and validation rules.

5 |
  //edit function
 6 |   cnv.editSalesforce(object,request,callback)
 7 |   //delete function
 8 |   cnv.deleteSalesforce(object, id, callback)
9 |

If the Id property is set in the request object, then the function will PATCH the specified record with the request object. If the Id property is not included in the request object, then a new record will be created via POST.

10 |

Example 2

11 |
  //create a new Task for tomorrow
12 |   var due = new Date();
13 |   //for tasks, we just pass the date part of the string.
14 |   due.setDate(due.getDate()+1).toISOString();
15 |   var request = {'ActivityDate':due.substring(0,10), 'Subject':'Test Task From the canvas-starter-kit'}
16 |   cnv.editSalesforce('Task', request, callback);
17 |
18 | 19 |
20 |
21 |
22 | Success!  Task Id: {{newId}}   23 |
24 |
25 | -------------------------------------------------------------------------------- /app/examples/events.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Canvas Events

4 |

The Canvas Framework provides tools for publishing and subcribing to events between Salesforce and Canvas. Your app is in your domain, so if we want to navigate in the Salesforce domain, then it's best to publish an event from your app to Salesforce and have the subscribing function in the Visualforce page handle it. You may have noticed the navigation working in the previous two examples using the cnv.navigate function. Understanding how the canvas-starter kit handles this navigation will get you a long way towards understanding Canvas Events. It works by setting up a subscription in the Visualforce page. In the kit, the JavaScript Static Resource for our Visualforce page starts the subscription on load. You can easily add additional Subscriptions to the visualforce page. The the kit's Static Resource JavaScript page canvas-static.js has a spot ready for this.

5 |
  //start subscribing in visualforce
 6 |   Sfdc.canvas.controller.subscribe(
 7 |     {
 8 |       "name": "cnvstart.navigate",
 9 |       "onData": navigate
10 |     });
11 |

Notice that in Visualforce we reference the canvas.controller object, whereas from your Canvas app, we reference canvas.client or canvas.oauth. We then publish this event from our canvas app like this. The canvas-starter.js file has the function cnv.publish(name,payload) to handle this.

12 |
  Sfdc.canvas.client.publish(storage.sr.client,
13 |   {
14 |     "name": "cnvstart.navigate",
15 |     "payload": {'id':id, 'new':true}
16 |   });
17 |
18 | -------------------------------------------------------------------------------- /app/examples/lightning.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Canvas In Lightning

4 |

Canvas apps as Visualforce page tabs work great in Lightning, and this kit has been set-up to look and work great in Lightning. Switch over to the Lightning experience (If you're not already there), and you'll see our resizing, navigation, queries and editing all work great. You'll find the kit in App Launcher/Other Items. Visualforce Tabs can be added to Navigation Menus.

5 |
6 | -------------------------------------------------------------------------------- /app/examples/oauth/oauth-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('OAuthCtrl', ['$scope','canvas', OAuthCtrl]); 7 | 8 | function OAuthCtrl($scope,canvas) { 9 | 10 | $scope.logout = logout; 11 | 12 | function logout(){ 13 | document.getElementById('logout').blur(); 14 | canvas.logout(true); 15 | } 16 | 17 | } 18 | 19 | }()); 20 | -------------------------------------------------------------------------------- /app/examples/oauth/oauth.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

OAuth

4 |

Canvas Apps need to provide an OAuth path for users to authenticate the app and get an access token for querying with Salesforce. Even if the Canvas app's access method is the Signed Request(like this one), an org has the option to have admins pre-authorize the app for certain profiles, or allow users to self-authourize. If the self-authorize option is chosen, then an OAuth path needs to be provided for the user. You will need to add your Consumer Key and your OAuth Callback endpoint to the consumerData.php file for this routine to work. The Consumer Key and callback endpoint are associated with your Connected/Canvas app during set-up. To initiate the OAuth process, call, or assign as a listener, cnv.login() from the canvas-static.js file.

5 |
6 |

To test your OAuth path, make sure the app is set to allow users to self authorize (setup/Managed Apps/Connected Apps), and click logout below. This will revoke the current access token and re-initialize the authorization pop-up and take you to the OAuth page where you can re-authorize. This button triggers cnv.logout(true)

7 |
8 |
9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /app/examples/overview.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Canvas App Starter Kit

4 |

This kit is designed for JavaScript developers (like us) who have an existing web application and would like to bring it to the Salesforce ecosphere. Canvas allows you to present your app(s) within the Salesforce domain and interact with Salesforce data, providing a seamless experience for your customers. Canvas apps can be the primary component of a Managed Package, allowing you to present your app on the Salesforce AppExchange. We have just completed bringing our DayBack application to the AppExchange, and this kit is a close approximation of the approach we took. This kit draws heavily on the Salesforce Documentation for Canvas, and the JavaScript Canvas SDK, which is included. We recommend becoming familar with both of these resources when working with this kit.

5 |

Using This Kit

6 |

This kit is built using Angular.js, and should be a good starting point for Angular developers. Building on top of it, for non-angular folks, should not cause any issues. The critical parts of this kit do not require Angular, and you can just include the canvas-starter.js and canvas-all.js files to take full advantage. The canvas-all.js file is the primary component of Salesforce's JavaScript SDK. The canvas-starter.js file is our example file, and should be a great starting point for your app's functions, and for taking advantage of the SDK. The chapters in this kit or for an overview and demonstration, for fuller documentation, refer to the repo on GitHub.

7 |

Installing this kit

8 |

You can get install instructions, and download or fork the kit, from GitHub. The kit contains a template for your web app, and the Package components we use in DayBack.

9 |
10 | -------------------------------------------------------------------------------- /app/examples/queryA/queryA-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('QueryACtrl', ['$scope','canvas', QueryACtrl]); 7 | 8 | function QueryACtrl($scope,canvas) { 9 | 10 | $scope.recentAccounts = recentAccounts; 11 | 12 | $scope.result=''; 13 | 14 | $scope.navigate = navigate; 15 | 16 | function recentAccounts() { 17 | document.getElementById('recentAccounts').blur(); 18 | var query = 'SELECT Id,Name,LastViewedDate FROM Account ORDER BY LastViewedDate DESC NULLS LAST LIMIT 6'; 19 | function process(result) { 20 | var date; 21 | //clean dates 22 | for (var i in result) { 23 | if(!result[i].LastViewedDate){ 24 | result[i].date='not viewed'; 25 | } 26 | else{ 27 | date = new Date(result[i].LastViewedDate); 28 | result[i].date=date.toLocaleDateString(); 29 | result[i].time=date.toLocaleTimeString(); 30 | } 31 | } 32 | $scope.$evalAsync(function(){ 33 | $scope.result = result; 34 | }); 35 | } 36 | canvas.querySalesforce(query,process); 37 | } 38 | 39 | function navigate(id) { 40 | cnv.navigate(id,null,true); 41 | } 42 | 43 | } 44 | 45 | }()); 46 | -------------------------------------------------------------------------------- /app/examples/queryA/queryA.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Querying Salesforce Data A

4 |

Once initialized, you can use the following function to query Salesforce. Refer to the Rest API Guide and the SOQL documentation for the correct query syntax.

5 |
  cnv.querySalesforce(query,callback)
6 |
  //query for my 6 most recently viewed accounts
 7 |   var query = 'SELECT Id,Name,LastViewedDate FROM Account ORDER BY LastViewedDate DESC NULLS LAST LIMIT 6'
 8 |   cnv.querySalesforce(query,callback);
9 |
10 | 11 |
12 |
13 | 16 |
17 | -------------------------------------------------------------------------------- /app/examples/queryB/queryB-controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app') 6 | .controller('QueryBCtrl', ['$scope','canvas', QueryBCtrl]); 7 | 8 | function QueryBCtrl($scope,canvas) { 9 | 10 | $scope.queryResult=''; 11 | 12 | $scope.runQuery = runQuery; 13 | 14 | $scope.clearResult = clearResult; 15 | 16 | $scope.queryEdit='SELECT Id,Name,LastViewedDate\nFROM Account\nORDER BY LastViewedDate DESC NULLS LAST\nLIMIT 6'; 17 | 18 | function runQuery(query) { 19 | document.getElementById('runQuery').blur(); 20 | function display(result) { 21 | $scope.$evalAsync(function(){ 22 | $scope.queryResult += JSON.stringify(result,null,2); 23 | }); 24 | } 25 | $scope.queryResult=''; 26 | canvas.querySalesforce(query,display,false); 27 | } 28 | 29 | function clearResult() { 30 | document.getElementById('clearResult').blur(); 31 | $scope.queryResult=''; 32 | } 33 | 34 | } 35 | 36 | }()); 37 | -------------------------------------------------------------------------------- /app/examples/queryB/queryB.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Querying Salesforce Data B

4 |

You can expirment with your SOQL here and see how the data (including error messages) is returned from the Canvas SDK below. Refer to the Object Reference for Salesforce for the Object and Field Names, and to the SOQL documentation for the correct query syntax.

5 |
6 | 7 |
8 |
9 | 10 | 11 |
12 |
{{queryResult}}
13 |
14 | -------------------------------------------------------------------------------- /app/examples/resizing.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Resizing the Canvas App

4 |

Resizing the Canvas app the way we wanted it was a little challenging, particularly for working in SF1 and Lightning, but you can see that this kit does a nice job resizing to fit the tab size. If is the behavior you're after, then our approach should work great for you. You can easily set the width of the Canvas element, in this case 100%, in the Visualforce page, but you can't set the height to a percentage, so you need to deal with that programatically.

5 |
6 |

Our method uses the canvas functions in the Visualforce page to resize the frame to fit the page/tab. The other approach would be if you want your app content to dictate the size of the canvas app on the page. In this case you do the resizing within the Canvas app itself.

7 |
8 |

As with Canvas Events, when working with the Canvas from the Visualforce page, we reference the canvas.controller object. In the following example, a function determines the height based on the platform, and the target is the Canvas element id assigned in the Visualforce page. The full resize function exists in our Static Resource JavaScript file canvas-static.js:

9 |
  Sfdc.canvas.controller.resize({
10 |     "height": height + "px"
11 |   }, target);
12 |

If we wanted to make the call from within the Canvas app, the syntax is very simmilar, but we would refernce canvas.client. We also don't need to specify the taret, as we're "in" the target. Size parameters are optional, and when not passed the frame attempts to resize based on content, but we had better luck calling the height explicitly. Since you're in your app's domain, you also need to include the Signed Request client object to authorize the call:

13 |
  Sfdc.canvas.client.resize(sr.client, {
14 |     "height": height + "px"
15 |   });
16 |
17 | -------------------------------------------------------------------------------- /app/examples/signed-request.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

The Signed Request

4 |

When the Signed Request access method is specified for your Canvas app, Salesforce requires a server side process to handle a POST to authorize the Canvas app and grant it access to interact with Salesforce. This kit uses the simplest possible solution for doing this, a PHP page that decodes the POST and launches your app (if authorized). We were very please do find this elegant solution by Joshua Birk on github. Our PHP file, canvas.php, is closely based on his solution. When you set up your Canvas app, you will need to direct it to the canvas.php page. You will also need to add the Consumer Secret, obtained from the Canvas app set-up, to the appropriate place in this page. This page will determine if the app is set to have users self-authorize, or if profiles are pre-authorized by an admin. If set to self-authorize, then the user will be sent to the OAuth page, where they can initiate the authorization process, once authorized they will be directed to the app. If pre-authorized, users will be taken directly to the app.

5 |
6 |

When the app loads, we use an Angular directive to call the routine which refreshes the Signed Request(SR) and stores it. We store the SR, because it contains the client object which is required for making calls via the JavaScript SDK. If you're not using angular, then you'll want to call cnv.initialize(callback) on load another way. The callback is not required, but confirms the process by returning the SR.

7 |
8 |

In addition to the client object, the SR also contains the context object, which contains information about the user, org, etc. You should see the context object below if the kit has succesfully retreived the SR. 9 |

{{context}}
10 |
11 | -------------------------------------------------------------------------------- /css/canvas-starter-styles.css: -------------------------------------------------------------------------------- 1 | h1.title { 2 | font-size: 2em; 3 | margin: 0.67em 0; 4 | } 5 | 6 | h2.sub-title { 7 | font-size: 1.3em; 8 | margin: 0.47em 0; 9 | } 10 | 11 | h3.sub-title { 12 | font-size: 1.1em; 13 | margin: 0.27em 0; 14 | } 15 | 16 | ul.result { 17 | height:140px; 18 | padding-left:24px; 19 | list-style-type: none; 20 | } 21 | 22 | li span{ 23 | float:left; 24 | } 25 | 26 | li span.column1 { 27 | width:35%; 28 | } 29 | 30 | li span.column2 { 31 | width:15%; 32 | } 33 | 34 | li.sidebar { 35 | list-style-type: circle; 36 | } 37 | 38 | li.sidebar.selected { 39 | font-weight:bold; 40 | } 41 | 42 | div.button-wrapper { 43 | color:rgb(0,112,210); 44 | } 45 | 46 | div.button-wrapper.oauth { 47 | width:30%;margin: 0 auto;text-align:center; 48 | } 49 | 50 | button { 51 | border: 1px solid rgb(216,221,230); 52 | background-color:white; 53 | font-size:.875rem; 54 | font-weight: 400; 55 | border-radius:.25rem; 56 | outline: none; 57 | padding:.4rem .5rem .4rem .5rem; 58 | } 59 | 60 | button:hover { 61 | background-color:rgb(230,230,230); 62 | border: 1px solid rgb(150,150,230); 63 | } 64 | 65 | button:focus { 66 | background-color:rgb(210,210,210); 67 | border: 1px solid rgb(150,150,230); 68 | } 69 | 70 | button.delete { 71 | padding:.1rem .5rem .1rem .5rem; 72 | vertical-align:10%; 73 | color:red; 74 | margin-left:12px; 75 | } 76 | 77 | div#oauth { 78 | width:30%; 79 | margin: 0 auto; 80 | margin-top:10%; 81 | text-align:center; 82 | } 83 | 84 | div#sidebar { 85 | display:inline-block; 86 | float:left; 87 | height:100%; 88 | padding-left:24px; 89 | padding-right:12px; 90 | width:348px; 91 | border-right-style:solid; 92 | border-right-width:0.5px; 93 | border-right-color:grey; 94 | } 95 | 96 | div#content { 97 | height:97%; 98 | padding-left:24px; 99 | padding-right:24px; 100 | overflow-y:auto; 101 | } 102 | 103 | span.success { 104 | font-weight:bold; 105 | color:green; 106 | } 107 | 108 | span.bold { 109 | font-weight:bolder; 110 | } 111 | -------------------------------------------------------------------------------- /df16_slides/AnyExistingWebApp.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/df16_slides/AnyExistingWebApp.key -------------------------------------------------------------------------------- /df16_slides/AnyExistingWebApp.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/df16_slides/AnyExistingWebApp.pptx -------------------------------------------------------------------------------- /fonts/License-for-font.txt: -------------------------------------------------------------------------------- 1 | FONT LICENSE AGREEMENT 2 | 3 | THIS FONT LICENSE AGREEMENT (“AGREEMENT”) IS A LEGAL AGREEMENT BETWEEN YOU AND 4 | SALESFORCE.COM, INC. (“WE”, “US”, “OUR”, AND “SALESFORCE”) THAT GOVERNS YOUR 5 | ACQUISITION AND USE OF THE SALESFORCE SANS FONT (E.G., TYPEFACE, TYPOGRAPHIC 6 | CHARACTERS, ALPHANUMERICS, SYMBOLS, DESIGNS, AND ORNAMENTS) AND THE RELATED 7 | FONT FILES (E.G., TRUETYPE (TTF), WEB OPEN FONT FORMAT (WOFF,WOFF2), EMBEDDED 8 | OPENTYPE (EOT), AND SCALABLE VECTOR GRAPHICS (SVG) FILES) (COLLECTIVELY, THE 9 | “FONT”). BY DOWNLOADING OR USING THE FONT, YOU AGREE TO THE TERMS OF THIS 10 | AGREEMENT. IF YOU ARE DOWNLOADING OR USING THE FONT ON BEHALF OF A COMPANY OR 11 | OTHER LEGAL ENTITY, YOU REPRESENT THAT YOU HAVE THE AUTHORITY TO BIND SUCH 12 | ENTITY AND ITS AFFILIATES TO THIS AGREEMENT, IN WHICH CASE THE TERMS “YOU” OR 13 | “YOUR” WILL REFER TO SUCH ENTITY AND ITS AFFILIATES. IF YOU DO NOT HAVE SUCH 14 | AUTHORITY, OR IF YOU DO NOT AGREE WITH THE TERMS OF THIS AGREEMENT, YOU MUST 15 | NOT DOWNLOAD OR USE THE FONT. This Agreement was last updated on August 21, 16 | 2015. It is effective between You and Us as of the date You accept this 17 | Agreement by downloading or using the Font. 18 | 19 | 1. License Grant 20 | Subject to the terms of this Agreement and any other 21 | applicable Salesforce terms, conditions, and acceptable use policies (AUPs), 22 | We hereby grant to You a revocable, non-transferable, non-exclusive, and non- 23 | sublicenseable limited license to, without modification, reproduce and use the 24 | Font solely to create applications with the Salesforce Lightning Design System 25 | that run in Salesforce or on a Salesforce platform (e.g., Lightning, Heroku, 26 | Visualforce) (“Applications”). 27 | 28 | 2. Restrictions 29 | To the extent your Application contains copyright notices, 30 | together with all other copyright notices included with each Application, You 31 | will include the following copyright notice: “The Salesforce Sans Font is used 32 | under license from salesforce.com, inc. Copyright 2015 Salesforce.com, Inc.” 33 | You may not modify, adapt, translate, reverse engineer, decompile, 34 | disassemble, or create derivative works based on the Font. You must include 35 | the Font in an Application in a manner that does not allow a user to access 36 | the Font outside of the Application. You will not use the Font on a 37 | standalone basis and will only use the Font as part of the Salesforce 38 | Lightning Design System. You must not take any action which will have the 39 | direct or indirect effect of causing the Font to become subject to the terms 40 | of an open source license or any similar terms. You may refer to the Font as 41 | “Salesforce Sans”, but You may not use any other Salesforce trademark in 42 | connection with the Font except as may be expressly agreed to by Salesforce in 43 | writing or as set forth in other applicable Salesforce terms, conditions, and 44 | acceptable use policies (AUPs) and in any event You must comply at all times 45 | with the Salesforce Trademark and Copyright Usage Guidelines located at http:/ 46 | /www2.sfdcstatic.com/assets/pdf/misc/salesforce_Trademark_Usage_Guidelines.pdf 47 | and any other supplemental guidelines that may apply to you. You must not 48 | license, sublicense, sell, resell, rent, lease, transfer, assign, distribute, 49 | time share, or otherwise commercially exploit the Font nor make the Font 50 | available to any third party, other than as expressly permitted by this 51 | Agreement. To the extent any Font documentation, style guides, or other 52 | applicable Salesforce terms, conditions, and acceptable use policies (AUPs) 53 | impose guidelines or restrictions for the use of the Font, You will abide by 54 | those guidelines and restrictions. 55 | 56 | 3. Ownership 57 | Subject to the limited rights expressly granted hereunder, We 58 | reserve all rights, title, and interest in and to the Font, including all 59 | related intellectual property rights. No rights are granted to You hereunder 60 | other than as expressly set forth herein. We shall have a royalty-free, 61 | worldwide, irrevocable, perpetual license to use and incorporate into the Font 62 | any suggestions, enhancement requests, recommendations, or other feedback 63 | provided by You. 64 | 65 | 4. Term and Termination 66 | This Agreement will take effect when you download or 67 | use the Font and will terminate upon the earlier of: (a) Your failure to 68 | comply with any term of this Agreement or any other applicable Salesforce 69 | terms, conditions, and acceptable use policies (AUPs); (b) return, 70 | destruction, or deletion of all copies of the Font in your possession; or, (c) 71 | 60 days after Salesforce provides You with written notice of termination. 72 | Salesforce’s rights and your obligations will survive the termination of this 73 | Agreement. Upon termination of this Agreement by Salesforce, if requested by 74 | Salesforce, you will destroy or delete all copies of the Font in your 75 | possession and cease using the Font in all Applications. 76 | 77 | 5. No Warranty 78 | THE FONT IS PROVIDED “AS-IS,” EXCLUSIVE OF ANY WARRANTY 79 | WHATSOEVER. WE DISCLAIM ALL IMPLIED WARRANTIES, INCLUDING WITHOUT LIMITATION 80 | ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, 81 | TITLE, AND NON-INFRINGEMENT. The Font may contain bugs or errors. Any use of 82 | the Font is at Your sole risk. You acknowledge that We may discontinue making 83 | the Font available to You at any time in Our sole discretion. 84 | 85 | 6. No Damages 86 | IN NO EVENT SHALL WE HAVE ANY LIABILITY HEREUNDER TO YOU FOR 87 | ANY DAMAGES WHATSOEVER, INCLUDING BUT NOT LIMITED TO DIRECT, INDIRECT, 88 | SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON 89 | LOST PROFITS, DATA OR USE, HOWEVER CAUSED AND, WHETHER IN CONTRACT, TORT OR 90 | UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF 91 | THE POSSIBILITY OF SUCH DAMAGES. 92 | 93 | 7. General Provisions 94 | You may not assign any of Your rights or obligations 95 | hereunder, whether by operation of law or otherwise, without Our prior written 96 | consent. This Agreement shall be governed exclusively by the internal laws of 97 | the State of California, without regard to its conflicts of laws rules. Each 98 | party hereby consents to the exclusive jurisdiction of the state and federal 99 | courts located in San Francisco County, California to adjudicate any dispute 100 | arising out of or relating to this Agreement. This Agreement constitutes the 101 | entire agreement between the parties, and supersedes all prior and 102 | contemporaneous agreements, proposals or representations, written or oral, 103 | concerning its subject matter. No modification, amendment, or waiver of any 104 | provision of this Agreement shall be effective unless in writing and either 105 | signed or accepted electronically by the party against whom the modification, 106 | amendment or waiver is to be asserted. 107 | -------------------------------------------------------------------------------- /fonts/SalesforceSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-Bold.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-BoldItalic.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-Italic.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-Light.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-LightItalic.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-Regular.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-Thin.ttf -------------------------------------------------------------------------------- /fonts/SalesforceSans-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/SalesforceSans-ThinItalic.ttf -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Bold.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Bold.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Bold.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-BoldItalic.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-BoldItalic.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Italic.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Italic.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Italic.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Light.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Light.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Light.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-LightItalic.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-LightItalic.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-LightItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-LightItalic.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Regular.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Regular.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Regular.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Thin.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Thin.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-Thin.woff2 -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-ThinItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-ThinItalic.eot -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-ThinItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-ThinItalic.woff -------------------------------------------------------------------------------- /fonts/webfonts/SalesforceSans-ThinItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/fonts/webfonts/SalesforceSans-ThinItalic.woff2 -------------------------------------------------------------------------------- /img/VF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seedcode/canvas-starter-kit/a4fafb11196779bb1c4702428f3aa75d9b96974c/img/VF.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Canvas Starter Kit for Salesforce 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 47 | 48 |
49 | 50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /libraries/canvas-all.js: -------------------------------------------------------------------------------- 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 | (function(global) { 27 | if(global.Sfdc && global.Sfdc.canvas && global.Sfdc.canvas.module) { 28 | return 29 | } 30 | var extmodules = {}; 31 | if(global.Sfdc && global.Sfdc.canvas) { 32 | for(var key in global.Sfdc.canvas) { 33 | if(global.Sfdc.canvas.hasOwnProperty(key)) { 34 | extmodules[key] = global.Sfdc.canvas[key] 35 | } 36 | } 37 | } 38 | var oproto = Object.prototype, aproto = Array.prototype, doc = global.document, keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\x3d", $ = {hasOwn:function(obj, prop) { 39 | return oproto.hasOwnProperty.call(obj, prop) 40 | }, isUndefined:function(value) { 41 | var undef; 42 | return value === undef 43 | }, isNil:function(value) { 44 | return $.isUndefined(value) || value === null || value === "" 45 | }, isNumber:function(value) { 46 | return!!(value === 0 || value && value.toExponential && value.toFixed) 47 | }, isFunction:function(value) { 48 | return!!(value && value.constructor && value.call && value.apply) 49 | }, isArray:Array.isArray || function(value) { 50 | return oproto.toString.call(value) === "[object Array]" 51 | }, isArguments:function(value) { 52 | return!!(value && $.hasOwn(value, "callee")) 53 | }, isObject:function(value) { 54 | return value !== null && typeof value === "object" 55 | }, isString:function(value) { 56 | return value !== null && typeof value == "string" 57 | }, appearsJson:function(value) { 58 | return/^\{.*\}$/.test(value) 59 | }, nop:function() { 60 | }, invoker:function(fn) { 61 | if($.isFunction(fn)) { 62 | fn() 63 | } 64 | }, identity:function(obj) { 65 | return obj 66 | }, each:function(obj, it, ctx) { 67 | if($.isNil(obj)) { 68 | return 69 | } 70 | var nativ = aproto.forEach, i = 0, l, key; 71 | l = obj.length; 72 | ctx = ctx || obj; 73 | if(nativ && nativ === obj.forEach) { 74 | obj.forEach(it, ctx) 75 | }else { 76 | if($.isNumber(l)) { 77 | while(i < l) { 78 | if(it.call(ctx, obj[i], i, obj) === false) { 79 | return 80 | } 81 | i += 1 82 | } 83 | }else { 84 | for(key in obj) { 85 | if($.hasOwn(obj, key) && it.call(ctx, obj[key], key, obj) === false) { 86 | return 87 | } 88 | } 89 | } 90 | } 91 | }, startsWithHttp:function(orig, newUrl) { 92 | return!$.isString(orig) ? orig : orig.substring(0, 4) === "http" ? orig : newUrl 93 | }, map:function(obj, it, ctx) { 94 | var results = [], nativ = aproto.map; 95 | if($.isNil(obj)) { 96 | return results 97 | } 98 | if(nativ && obj.map === nativ) { 99 | return obj.map(it, ctx) 100 | } 101 | ctx = ctx || obj; 102 | $.each(obj, function(value, i, list) { 103 | results.push(it.call(ctx, value, i, list)) 104 | }); 105 | return results 106 | }, values:function(obj) { 107 | return $.map(obj, $.identity) 108 | }, slice:function(array, begin, end) { 109 | return aproto.slice.call(array, $.isUndefined(begin) ? 0 : begin, $.isUndefined(end) ? array.length : end) 110 | }, toArray:function(iterable) { 111 | if(!iterable) { 112 | return[] 113 | } 114 | if(iterable.toArray) { 115 | return iterable.toArray 116 | } 117 | if($.isArray(iterable)) { 118 | return iterable 119 | } 120 | if($.isArguments(iterable)) { 121 | return $.slice(iterable) 122 | } 123 | return $.values(iterable) 124 | }, size:function(obj) { 125 | return $.toArray(obj).length 126 | }, indexOf:function(array, item) { 127 | var nativ = aproto.indexOf, i, l; 128 | if(!array) { 129 | return-1 130 | } 131 | if(nativ && array.indexOf === nativ) { 132 | return array.indexOf(item) 133 | } 134 | for(i = 0, l = array.length;i < l;i += 1) { 135 | if(array[i] === item) { 136 | return i 137 | } 138 | } 139 | return-1 140 | }, isEmpty:function(obj) { 141 | if(obj === null) { 142 | return true 143 | } 144 | if($.isArray(obj) || $.isString(obj)) { 145 | return obj.length === 0 146 | } 147 | for(var key in obj) { 148 | if($.hasOwn(obj, key)) { 149 | return false 150 | } 151 | } 152 | return true 153 | }, remove:function(array, item) { 154 | var i = $.indexOf(array, item); 155 | if(i >= 0) { 156 | array.splice(i, 1) 157 | } 158 | }, param:function(a, encode) { 159 | var s = []; 160 | encode = encode || false; 161 | function add(key, value) { 162 | if($.isNil(value)) { 163 | return 164 | } 165 | value = $.isFunction(value) ? value() : value; 166 | if($.isArray(value)) { 167 | $.each(value, function(v, n) { 168 | add(key, v) 169 | }) 170 | }else { 171 | if(encode) { 172 | s[s.length] = encodeURIComponent(key) + "\x3d" + encodeURIComponent(value) 173 | }else { 174 | s[s.length] = key + "\x3d" + value 175 | } 176 | } 177 | } 178 | if($.isArray(a)) { 179 | $.each(a, function(v, n) { 180 | add(n, v) 181 | }) 182 | }else { 183 | for(var p in a) { 184 | if($.hasOwn(a, p)) { 185 | add(p, a[p]) 186 | } 187 | } 188 | } 189 | return s.join("\x26").replace(/%20/g, "+") 190 | }, objectify:function(q) { 191 | var arr, obj = {}, i, p, n, v, e; 192 | if($.isNil(q)) { 193 | return obj 194 | } 195 | if(q.substring(0, 1) == "?") { 196 | q = q.substring(1) 197 | } 198 | arr = q.split("\x26"); 199 | for(i = 0;i < arr.length;i += 1) { 200 | p = arr[i].split("\x3d"); 201 | n = p[0]; 202 | v = p[1]; 203 | e = obj[n]; 204 | if(!$.isNil(e)) { 205 | if($.isArray(e)) { 206 | e[e.length] = v 207 | }else { 208 | obj[n] = []; 209 | obj[n][0] = e; 210 | obj[n][1] = v 211 | } 212 | }else { 213 | obj[n] = v 214 | } 215 | } 216 | return obj 217 | }, stripUrl:function(url) { 218 | return $.isNil(url) ? null : url.replace(/([^:]+:\/\/[^\/\?#]+).*/, "$1") 219 | }, query:function(url, q) { 220 | if($.isNil(q)) { 221 | return url 222 | } 223 | url = url.replace(/#.*$/, ""); 224 | url += /^\#/.test(q) ? q : (/\?/.test(url) ? "\x26" : "?") + q; 225 | return url 226 | }, extend:function(dest) { 227 | $.each($.slice(arguments, 1), function(mixin, i) { 228 | $.each(mixin, function(value, key) { 229 | dest[key] = value 230 | }) 231 | }); 232 | return dest 233 | }, endsWith:function(str, suffix) { 234 | return str.indexOf(suffix, str.length - suffix.length) !== -1 235 | }, capitalize:function(str) { 236 | return str.charAt(0).toUpperCase() + str.slice(1) 237 | }, uncapitalize:function(str) { 238 | return str.charAt(0).toLowerCase() + str.slice(1) 239 | }, decode:function(str) { 240 | var output = [], chr1, chr2, chr3 = "", enc1, enc2, enc3, enc4 = "", i = 0; 241 | str = str.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 242 | do { 243 | enc1 = keyStr.indexOf(str.charAt(i++)); 244 | enc2 = keyStr.indexOf(str.charAt(i++)); 245 | enc3 = keyStr.indexOf(str.charAt(i++)); 246 | enc4 = keyStr.indexOf(str.charAt(i++)); 247 | chr1 = enc1 << 2 | enc2 >> 4; 248 | chr2 = (enc2 & 15) << 4 | enc3 >> 2; 249 | chr3 = (enc3 & 3) << 6 | enc4; 250 | output.push(String.fromCharCode(chr1)); 251 | if(enc3 !== 64) { 252 | output.push(String.fromCharCode(chr2)) 253 | } 254 | if(enc4 !== 64) { 255 | output.push(String.fromCharCode(chr3)) 256 | } 257 | chr1 = chr2 = chr3 = ""; 258 | enc1 = enc2 = enc3 = enc4 = "" 259 | }while(i < str.length); 260 | return $.escapeToUTF8(output.join("")) 261 | }, escapeToUTF8:function(str) { 262 | var outStr = ""; 263 | var i = 0; 264 | while(i < str.length) { 265 | var c = str.charCodeAt(i++); 266 | var c1; 267 | if(c < 128) { 268 | outStr += String.fromCharCode(c) 269 | }else { 270 | if(c > 191 && c < 224) { 271 | c1 = str.charCodeAt(i++); 272 | outStr += String.fromCharCode((c & 31) << 6 | c1 & 63) 273 | }else { 274 | c1 = str.charCodeAt(i++); 275 | var c2 = str.charCodeAt(i++); 276 | outStr += String.fromCharCode((c & 15) << 12 | (c1 & 63) << 6 | c2 & 63) 277 | } 278 | } 279 | } 280 | return outStr 281 | }, validEventName:function(name, res) { 282 | var ns, parts = name.split(/\./), regex = /^[$A-Z_][0-9A-Z_$]*$/i, reserved = {"sfdc":true, "canvas":true, "force":true, "salesforce":true, "chatter":true}; 283 | $.each($.isArray(res) ? res : [res], function(v) { 284 | reserved[v] = false 285 | }); 286 | if(parts.length > 2) { 287 | return 1 288 | } 289 | if(parts.length === 2) { 290 | ns = parts[0].toLowerCase(); 291 | if(reserved[ns]) { 292 | return 2 293 | } 294 | } 295 | if(!regex.test(parts[0]) || !regex.test(parts[1])) { 296 | return 3 297 | } 298 | return 0 299 | }, prototypeOf:function(obj) { 300 | var nativ = Object.getPrototypeOf, proto = "__proto__"; 301 | if($.isFunction(nativ)) { 302 | return nativ.call(Object, obj) 303 | }else { 304 | if(typeof{}[proto] === "object") { 305 | return obj[proto] 306 | }else { 307 | return obj.constructor.prototype 308 | } 309 | } 310 | }, module:function(ns, decl) { 311 | var parts = ns.split("."), parent = global.Sfdc.canvas, i, length; 312 | if(parts[1] === "canvas") { 313 | parts = parts.slice(2) 314 | } 315 | length = parts.length; 316 | for(i = 0;i < length;i += 1) { 317 | if($.isUndefined(parent[parts[i]])) { 318 | parent[parts[i]] = {} 319 | } 320 | parent = parent[parts[i]] 321 | } 322 | if($.isFunction(decl)) { 323 | decl = decl() 324 | } 325 | return $.extend(parent, decl) 326 | }, document:function() { 327 | return doc 328 | }, byId:function(id) { 329 | return doc.getElementById(id) 330 | }, byClass:function(clazz) { 331 | return doc.getElementsByClassName(clazz) 332 | }, attr:function(el, name) { 333 | var a = el.attributes, i; 334 | for(i = 0;i < a.length;i += 1) { 335 | if(name === a[i].name) { 336 | return a[i].value 337 | } 338 | } 339 | }, onReady:function(cb) { 340 | if($.isFunction(cb)) { 341 | readyHandlers.push(cb) 342 | } 343 | }, console:function() { 344 | var enabled = false; 345 | if(window && !window.console) { 346 | window.console = {} 347 | } 348 | if(window && !window.console.log) { 349 | window.console.log = function() { 350 | } 351 | } 352 | if(window && !window.console.error) { 353 | window.console.error = function() { 354 | } 355 | } 356 | function isSessionStorage() { 357 | try { 358 | return"sessionStorage" in window && window.sessionStorage !== null 359 | }catch(e) { 360 | return false 361 | } 362 | } 363 | function log() { 364 | } 365 | function error() { 366 | } 367 | function activate() { 368 | if(Function.prototype.bind) { 369 | log = Function.prototype.bind.call(console.log, console); 370 | error = Function.prototype.bind.call(console.error, console) 371 | }else { 372 | log = function() { 373 | Function.prototype.apply.call(console.log, console, arguments) 374 | }; 375 | error = function() { 376 | Function.prototype.apply.call(console.error, console, arguments) 377 | } 378 | } 379 | } 380 | function deactivate() { 381 | log = function() { 382 | }; 383 | error = function() { 384 | } 385 | } 386 | function enable() { 387 | enabled = true; 388 | if(isSessionStorage()) { 389 | sessionStorage.setItem("canvas_console", "true") 390 | } 391 | activate() 392 | } 393 | function disable() { 394 | enabled = false; 395 | if(isSessionStorage()) { 396 | sessionStorage.setItem("canvas_console", "false") 397 | } 398 | deactivate() 399 | } 400 | enabled = isSessionStorage() && sessionStorage.getItem("canvas_console") === "true"; 401 | if(enabled) { 402 | activate() 403 | }else { 404 | deactivate() 405 | } 406 | return{enable:enable, disable:disable, log:log, error:error} 407 | }()}, readyHandlers = [], canvas = function(cb) { 408 | if($.isFunction(cb)) { 409 | readyHandlers.push(cb) 410 | } 411 | }; 412 | (function() { 413 | var called = false, isFrame, fn; 414 | function ready() { 415 | if(called) { 416 | return 417 | } 418 | called = true; 419 | ready = $.nop; 420 | $.each(readyHandlers, $.invoker); 421 | readyHandlers = [] 422 | } 423 | function tryScroll() { 424 | if(called) { 425 | return 426 | } 427 | try { 428 | document.documentElement.doScroll("left"); 429 | ready() 430 | }catch(e) { 431 | setTimeout(tryScroll, 30) 432 | } 433 | } 434 | if(document.addEventListener) { 435 | document.addEventListener("DOMContentLoaded", ready, false) 436 | }else { 437 | if(document.attachEvent) { 438 | try { 439 | isFrame = self !== top 440 | }catch(e) { 441 | } 442 | if(document.documentElement.doScroll && !isFrame) { 443 | tryScroll() 444 | } 445 | document.attachEvent("onreadystatechange", function() { 446 | if(document.readyState === "complete") { 447 | ready() 448 | } 449 | }) 450 | } 451 | } 452 | if(window.addEventListener) { 453 | window.addEventListener("load", ready, false) 454 | }else { 455 | if(window.attachEvent) { 456 | window.attachEvent("onload", ready) 457 | }else { 458 | fn = window.onload; 459 | window.onload = function() { 460 | if(fn) { 461 | fn() 462 | } 463 | ready() 464 | } 465 | } 466 | } 467 | })(); 468 | $.each($, function(fn, name) { 469 | canvas[name] = fn 470 | }); 471 | $.each(extmodules, function(fn, name) { 472 | canvas[name] = fn 473 | }); 474 | (function() { 475 | var method; 476 | var noop = function() { 477 | }; 478 | var methods = ["assert", "clear", "count", "debug", "dir", "dirxml", "error", "exception", "group", "groupCollapsed", "groupEnd", "info", "log", "markTimeline", "profile", "profileEnd", "table", "time", "timeEnd", "timeStamp", "trace", "warn"]; 479 | var length = methods.length; 480 | var console = typeof window !== "undefined" && window.console ? window.console : {}; 481 | while(length--) { 482 | method = methods[length]; 483 | if(!console[method]) { 484 | console[method] = noop 485 | } 486 | } 487 | })(); 488 | if(!global.Sfdc) { 489 | global.Sfdc = {} 490 | } 491 | global.Sfdc.canvas = canvas 492 | })(this); 493 | (function($$) { 494 | var module = function() { 495 | function isSecure() { 496 | return window.location.protocol === "https:" 497 | } 498 | function set(name, value, days) { 499 | var expires = "", date; 500 | if(days) { 501 | date = new Date; 502 | date.setTime(date.getTime() + days * 24 * 60 * 60 * 1E3); 503 | expires = "; expires\x3d" + date.toGMTString() 504 | }else { 505 | expires = "" 506 | } 507 | document.cookie = name + "\x3d" + value + expires + "; path\x3d/" + (isSecure() === true ? "; secure" : "") 508 | } 509 | function get(name) { 510 | var nameEQ, ca, c, i; 511 | if($$.isUndefined(name)) { 512 | return document.cookie.split(";") 513 | } 514 | nameEQ = name + "\x3d"; 515 | ca = document.cookie.split(";"); 516 | for(i = 0;i < ca.length;i += 1) { 517 | c = ca[i]; 518 | while(c.charAt(0) === " ") { 519 | c = c.substring(1, c.length) 520 | } 521 | if(c.indexOf(nameEQ) === 0) { 522 | return c.substring(nameEQ.length, c.length) 523 | } 524 | } 525 | return null 526 | } 527 | function remove(name) { 528 | set(name, "", -1) 529 | } 530 | return{set:set, get:get, remove:remove} 531 | }(); 532 | $$.module("Sfdc.canvas.cookies", module) 533 | })(Sfdc.canvas); 534 | (function($$) { 535 | var storage = function() { 536 | function isLocalStorage() { 537 | try { 538 | return"sessionStorage" in window && window.sessionStorage !== null 539 | }catch(e) { 540 | return false 541 | } 542 | } 543 | return{get:function get(key) { 544 | if(isLocalStorage()) { 545 | return sessionStorage.getItem(key) 546 | } 547 | return $$.cookies.get(key) 548 | }, set:function set(key, value) { 549 | if(isLocalStorage()) { 550 | return sessionStorage.setItem(key, value) 551 | } 552 | return $$.cookies.set(key, value) 553 | }, remove:function remove(key) { 554 | if(isLocalStorage()) { 555 | return sessionStorage.removeItem(key) 556 | } 557 | return $$.cookies.remove(key) 558 | }} 559 | }(); 560 | var module = function() { 561 | var accessToken, instUrl, instId, tOrigin, childWindow; 562 | function init() { 563 | accessToken = storage.get("access_token"); 564 | storage.remove("access_token") 565 | } 566 | function query(params) { 567 | var r = [], n; 568 | if(!$$.isUndefined(params)) { 569 | for(n in params) { 570 | if(params.hasOwnProperty(n)) { 571 | r.push(n + "\x3d" + params[n]) 572 | } 573 | } 574 | return"?" + r.join("\x26") 575 | } 576 | return"" 577 | } 578 | function refresh() { 579 | storage.set("access_token", accessToken); 580 | self.location.reload() 581 | } 582 | function login(ctx) { 583 | var uri; 584 | ctx = ctx || {}; 585 | uri = ctx.uri || "/rest/oauth2"; 586 | ctx.params = ctx.params || {state:""}; 587 | ctx.params.state = ctx.params.state || ctx.callback || window.location.pathname; 588 | ctx.params.display = ctx.params.display || "popup"; 589 | ctx.params.redirect_uri = $$.startsWithHttp(ctx.params.redirect_uri, encodeURIComponent(window.location.protocol + "//" + window.location.hostname + ":" + window.location.port) + ctx.params.redirect_uri); 590 | uri = uri + query(ctx.params); 591 | childWindow = window.open(uri, "OAuth", "status\x3d0,toolbar\x3d0,menubar\x3d0,resizable\x3d0,scrollbars\x3d1,top\x3d50,left\x3d50,height\x3d500,width\x3d680") 592 | } 593 | function token(t) { 594 | if(arguments.length === 0) { 595 | if(!$$.isNil(accessToken)) { 596 | return accessToken 597 | } 598 | }else { 599 | accessToken = t 600 | } 601 | return accessToken 602 | } 603 | function instanceUrl(i) { 604 | if(arguments.length === 0) { 605 | if(!$$.isNil(instUrl)) { 606 | return instUrl 607 | } 608 | instUrl = storage.get("instance_url") 609 | }else { 610 | if(i === null) { 611 | storage.remove("instance_url"); 612 | instUrl = null 613 | }else { 614 | storage.set("instance_url", i); 615 | instUrl = i 616 | } 617 | } 618 | return instUrl 619 | } 620 | function parseHash(hash) { 621 | var i, nv, nvp, n, v; 622 | if(!$$.isNil(hash)) { 623 | if(hash.indexOf("#") === 0) { 624 | hash = hash.substr(1) 625 | } 626 | nvp = hash.split("\x26"); 627 | for(i = 0;i < nvp.length;i += 1) { 628 | nv = nvp[i].split("\x3d"); 629 | n = nv[0]; 630 | v = decodeURIComponent(nv[1]); 631 | if("access_token" === n) { 632 | token(v) 633 | }else { 634 | if("instance_url" === n) { 635 | instanceUrl(v) 636 | }else { 637 | if("target_origin" === n) { 638 | tOrigin = decodeURIComponent(v) 639 | }else { 640 | if("instance_id" === n) { 641 | instId = v 642 | } 643 | } 644 | } 645 | } 646 | } 647 | } 648 | } 649 | function checkChildWindowStatus() { 650 | if(!childWindow || childWindow.closed) { 651 | refresh() 652 | } 653 | } 654 | function childWindowUnloadNotification(hash) { 655 | var retry = 0, maxretries = 10; 656 | function cws() { 657 | retry++; 658 | if(!childWindow || childWindow.closed) { 659 | refresh() 660 | }else { 661 | if(retry < maxretries) { 662 | setTimeout(cws, 50) 663 | } 664 | } 665 | } 666 | parseHash(hash); 667 | setTimeout(cws, 50) 668 | } 669 | function logout() { 670 | token(null) 671 | } 672 | function loggedin() { 673 | return!$$.isNil(token()) 674 | } 675 | function loginUrl() { 676 | var i, nvs, nv, q = self.location.search; 677 | if(q) { 678 | q = q.substring(1); 679 | nvs = q.split("\x26"); 680 | for(i = 0;i < nvs.length;i += 1) { 681 | nv = nvs[i].split("\x3d"); 682 | if("loginUrl" === nv[0]) { 683 | return decodeURIComponent(nv[1]) + "/services/oauth2/authorize" 684 | } 685 | } 686 | } 687 | return"https://login.salesforce.com/services/oauth2/authorize" 688 | } 689 | function targetOrigin(to) { 690 | if(!$$.isNil(to)) { 691 | tOrigin = to; 692 | return to 693 | } 694 | if(!$$.isNil(tOrigin)) { 695 | return tOrigin 696 | } 697 | parseHash(document.location.hash); 698 | return tOrigin 699 | } 700 | function instanceId(id) { 701 | if(!$$.isNil(id)) { 702 | instId = id; 703 | return id 704 | } 705 | if(!$$.isNil(instId)) { 706 | return instId 707 | } 708 | parseHash(document.location.hash); 709 | return instId 710 | } 711 | function client() { 712 | return{oauthToken:token(), instanceId:instanceId(), targetOrigin:targetOrigin()} 713 | } 714 | return{init:init, login:login, logout:logout, loggedin:loggedin, loginUrl:loginUrl, token:token, instance:instanceUrl, client:client, checkChildWindowStatus:checkChildWindowStatus, childWindowUnloadNotification:childWindowUnloadNotification} 715 | }(); 716 | $$.module("Sfdc.canvas.oauth", module); 717 | $$.oauth.init() 718 | })(Sfdc.canvas); 719 | (function($$, window) { 720 | var module = function() { 721 | var internalCallback; 722 | function postMessage(message, target_url, target) { 723 | var sfdcJson = Sfdc.JSON || JSON; 724 | if($$.isNil(target_url)) { 725 | throw"ERROR: target_url was not supplied on postMessage"; 726 | } 727 | var otherWindow = $$.stripUrl(target_url); 728 | target = target || parent; 729 | if(window.postMessage) { 730 | if($$.isObject(message)) { 731 | message.targetModule = "Canvas" 732 | } 733 | message = sfdcJson.stringify(message); 734 | $$.console.log("Sending Post Message ", message); 735 | target.postMessage(message, otherWindow) 736 | } 737 | } 738 | function receiveMessage(callback, source_origin) { 739 | if(window.postMessage) { 740 | if(callback) { 741 | internalCallback = function(e) { 742 | var data, r; 743 | var sfdcJson = Sfdc.JSON || JSON; 744 | $$.console.log("Post Message Got callback", e); 745 | if(!$$.isNil(e)) { 746 | if(typeof source_origin === "string" && e.origin !== source_origin) { 747 | $$.console.log("source origin's don't match", e.origin, source_origin); 748 | return false 749 | } 750 | if($$.isFunction(source_origin)) { 751 | r = source_origin(e.origin, e.data); 752 | if(r === false) { 753 | $$.console.log("source origin's function returning false", e.origin, e.data); 754 | return false 755 | } 756 | } 757 | if($$.appearsJson(e.data)) { 758 | try { 759 | data = sfdcJson.parse(e.data) 760 | }catch(ignore) { 761 | } 762 | if(!$$.isNil(data) && ($$.isNil(data.targetModule) || data.targetModule === "Canvas")) { 763 | $$.console.log("Invoking callback"); 764 | callback(data, r) 765 | } 766 | } 767 | } 768 | } 769 | } 770 | if(window.addEventListener) { 771 | window.addEventListener("message", internalCallback, false) 772 | }else { 773 | window.attachEvent("onmessage", internalCallback) 774 | } 775 | } 776 | } 777 | function removeListener() { 778 | if(window.postMessage) { 779 | if(window.removeEventListener) { 780 | window.removeEventListener("message", internalCallback, false) 781 | }else { 782 | window.detachEvent("onmessage", internalCallback) 783 | } 784 | } 785 | } 786 | return{post:postMessage, receive:receiveMessage, remove:removeListener} 787 | }(); 788 | $$.module("Sfdc.canvas.xd", module) 789 | })(Sfdc.canvas, this); 790 | (function($$) { 791 | var pversion, cversion = "31.0"; 792 | var module = function() { 793 | var purl; 794 | function getTargetOrigin(to) { 795 | var h; 796 | if(to === "*") { 797 | return to 798 | } 799 | if(!$$.isNil(to)) { 800 | h = $$.stripUrl(to); 801 | purl = $$.startsWithHttp(h, purl); 802 | if(purl) { 803 | return purl 804 | } 805 | } 806 | h = $$.document().location.hash; 807 | if(h) { 808 | h = decodeURIComponent(h.replace(/^#/, "")); 809 | purl = $$.startsWithHttp(h, purl) 810 | } 811 | return purl 812 | } 813 | function xdCallback(data) { 814 | if(data) { 815 | if(submodules[data.type]) { 816 | submodules[data.type].callback(data) 817 | } 818 | } 819 | } 820 | var submodules = function() { 821 | var cbs = [], seq = 0, autog = true; 822 | function postit(clientscb, message) { 823 | var wrapped, to, c; 824 | seq = seq > 100 ? 0 : seq + 1; 825 | cbs[seq] = clientscb; 826 | wrapped = {seq:seq, src:"client", clientVersion:cversion, parentVersion:pversion, body:message}; 827 | c = message && message.config && message.config.client; 828 | to = getTargetOrigin($$.isNil(c) ? null : c.targetOrigin); 829 | if($$.isNil(to)) { 830 | 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."; 831 | } 832 | $$.console.log("posting message ", {message:wrapped, to:to}); 833 | $$.xd.post(wrapped, to, parent) 834 | } 835 | function validateClient(client, cb) { 836 | var msg; 837 | client = client || $$.oauth && $$.oauth.client(); 838 | if($$.isNil(client) || $$.isNil(client.oauthToken)) { 839 | msg = {status:401, statusText:"Unauthorized", parentVersion:pversion, payload:"client or client.oauthToken not supplied"} 840 | } 841 | if($$.isNil(client.instanceId) || $$.isNil(client.targetOrigin)) { 842 | msg = {status:400, statusText:"Bad Request", parentVersion:pversion, payload:"client.instanceId or client.targetOrigin not supplied"} 843 | } 844 | if(!$$.isNil(msg)) { 845 | if($$.isFunction(cb)) { 846 | cb(msg); 847 | return false 848 | }else { 849 | throw msg; 850 | } 851 | } 852 | return true 853 | } 854 | var event = function() { 855 | var subscriptions = {}, STR_EVT = "sfdc.streamingapi"; 856 | function validName(name, res) { 857 | var msg, r = $$.validEventName(name, res); 858 | if(r !== 0) { 859 | msg = {1:"Event names can only contain one namespace", 2:"Namespace has already been reserved", 3:"Event name contains invalid characters"}; 860 | throw msg[r]; 861 | } 862 | } 863 | function findSubscription(event) { 864 | var s, name = event.name; 865 | if(name === STR_EVT) { 866 | if(!$$.isNil(subscriptions[name])) { 867 | s = subscriptions[name][event.params.topic] 868 | } 869 | }else { 870 | s = subscriptions[name] 871 | } 872 | if(!$$.isNil(s) && ($$.isFunction(s.onData) || $$.isFunction(s.onComplete))) { 873 | return s 874 | } 875 | return null 876 | } 877 | return{callback:function(data) { 878 | var event = data.payload, subscription = findSubscription(event), func; 879 | if(!$$.isNil(subscription)) { 880 | if(event.method === "onData") { 881 | func = subscription.onData 882 | }else { 883 | if(event.method === "onComplete") { 884 | func = subscription.onComplete 885 | } 886 | } 887 | if(!$$.isNil(func) && $$.isFunction(func)) { 888 | func(event.payload) 889 | } 890 | } 891 | }, subscribe:function(client, s) { 892 | var subs = {}; 893 | if($$.isNil(s) || !validateClient(client)) { 894 | throw"precondition fail"; 895 | } 896 | $$.each($$.isArray(s) ? s : [s], function(v) { 897 | if(!$$.isNil(v.name)) { 898 | validName(v.name, ["canvas", "sfdc"]); 899 | if(v.name === STR_EVT) { 900 | if(!$$.isNil(v.params) && !$$.isNil(v.params.topic)) { 901 | if($$.isNil(subscriptions[v.name])) { 902 | subscriptions[v.name] = {} 903 | } 904 | subscriptions[v.name][v.params.topic] = v 905 | }else { 906 | throw"[" + STR_EVT + "] topic is missing"; 907 | } 908 | }else { 909 | subscriptions[v.name] = v 910 | } 911 | subs[v.name] = {params:v.params} 912 | }else { 913 | throw"subscription does not have a 'name'"; 914 | } 915 | }); 916 | if(!client.isVF) { 917 | postit(null, {type:"subscribe", config:{client:client}, subscriptions:subs}) 918 | } 919 | }, unsubscribe:function(client, s) { 920 | var subs = {}; 921 | if($$.isNil(s) || !validateClient(client)) { 922 | throw"PRECONDITION FAIL: need fo supply client and event name"; 923 | } 924 | if($$.isString(s)) { 925 | subs[s] = {}; 926 | delete subscriptions[s] 927 | }else { 928 | $$.each($$.isArray(s) ? s : [s], function(v) { 929 | var name = v.name ? v.name : v; 930 | validName(name, ["canvas", "sfdc"]); 931 | subs[name] = {params:v.params}; 932 | if(name === STR_EVT) { 933 | if(!$$.isNil(subscriptions[name])) { 934 | if(!$$.isNil(subscriptions[name][v.params.topic])) { 935 | delete subscriptions[name][v.params.topic] 936 | } 937 | if($$.size(subscriptions[name]) <= 0) { 938 | delete subscriptions[name] 939 | } 940 | } 941 | }else { 942 | delete subscriptions[name] 943 | } 944 | }) 945 | } 946 | if(!client.isVF) { 947 | postit(null, {type:"unsubscribe", config:{client:client}, subscriptions:subs}) 948 | } 949 | }, publish:function(client, e) { 950 | if(!$$.isNil(e) && !$$.isNil(e.name)) { 951 | validName(e.name); 952 | if(validateClient(client)) { 953 | postit(null, {type:"publish", config:{client:client}, event:e}) 954 | } 955 | } 956 | }} 957 | }(); 958 | var callback = function() { 959 | return{callback:function(data) { 960 | if(data.status === 401 && $$.isArray(data.payload) && data.payload[0].errorCode && data.payload[0].errorCode === "INVALID_SESSION_ID") { 961 | if($$.oauth) { 962 | $$.oauth.logout() 963 | } 964 | } 965 | if($$.isFunction(cbs[data.seq])) { 966 | if(!$$.isFunction(cbs[data.seq])) { 967 | alert("not function") 968 | } 969 | cbs[data.seq](data) 970 | }else { 971 | } 972 | }} 973 | }(); 974 | var services = function() { 975 | var sr; 976 | return{ajax:function(url, settings) { 977 | var ccb, config, defaults; 978 | if(!url) { 979 | throw"PRECONDITION ERROR: url required with AJAX call"; 980 | } 981 | if(!settings || !$$.isFunction(settings.success)) { 982 | throw"PRECONDITION ERROR: function: 'settings.success' missing."; 983 | } 984 | if(!validateClient(settings.client, settings.success)) { 985 | return 986 | } 987 | ccb = settings.success; 988 | defaults = {method:"GET", async:true, contentType:"application/json", headers:{"Authorization":"OAuth " + settings.client.oauthToken, "Accept":"application/json"}, data:null}; 989 | config = $$.extend(defaults, settings || {}); 990 | config.success = undefined; 991 | config.failure = undefined; 992 | if(config.client.targetOrigin === "*") { 993 | config.client.targetOrigin = null 994 | }else { 995 | purl = $$.startsWithHttp(config.targetOrigin, purl) 996 | } 997 | postit(ccb, {type:"ajax", url:url, config:config}) 998 | }, ctx:function(clientscb, client) { 999 | if(validateClient(client, clientscb)) { 1000 | postit(clientscb, {type:"ctx", accessToken:client.oauthToken, config:{client:client}}) 1001 | } 1002 | }, token:function(t) { 1003 | return $$.oauth && $$.oauth.token(t) 1004 | }, version:function() { 1005 | return{clientVersion:cversion, parentVersion:pversion} 1006 | }, signedrequest:function(s) { 1007 | if(arguments.length > 0) { 1008 | sr = s 1009 | } 1010 | return sr 1011 | }, refreshSignedRequest:function(clientscb) { 1012 | var id = window.name.substring("canvas-frame-".length), client = {oauthToken:"null", instanceId:id, targetOrigin:"*"}; 1013 | postit(clientscb, {type:"refresh", accessToken:client.oauthToken, config:{client:client}}) 1014 | }, repost:function(refresh) { 1015 | var id = window.name.substring("canvas-frame-".length), client = {oauthToken:"null", instanceId:id, targetOrigin:"*"}, r = refresh || false; 1016 | postit(null, {type:"repost", accessToken:client.oauthToken, config:{client:client}, refresh:r}) 1017 | }} 1018 | }(); 1019 | var frame = function() { 1020 | return{size:function() { 1021 | var docElement = $$.document().documentElement; 1022 | var contentHeight = docElement.scrollHeight, pageHeight = docElement.clientHeight, scrollTop = docElement && docElement.scrollTop || $$.document().body.scrollTop, contentWidth = docElement.scrollWidth, pageWidth = docElement.clientWidth, scrollLeft = docElement && docElement.scrollLeft || $$.document().body.scrollLeft; 1023 | return{heights:{contentHeight:contentHeight, pageHeight:pageHeight, scrollTop:scrollTop}, widths:{contentWidth:contentWidth, pageWidth:pageWidth, scrollLeft:scrollLeft}} 1024 | }, resize:function(client, size) { 1025 | var sh, ch, sw, cw, s = {height:"", width:""}, docElement = $$.document().documentElement; 1026 | if($$.isNil(size)) { 1027 | sh = docElement.scrollHeight; 1028 | ch = docElement.clientHeight; 1029 | if(ch !== sh) { 1030 | s.height = sh + "px" 1031 | } 1032 | sw = docElement.scrollWidth; 1033 | cw = docElement.clientWidth; 1034 | if(sw !== cw) { 1035 | s.width = sw + "px" 1036 | } 1037 | }else { 1038 | if(!$$.isNil(size.height)) { 1039 | s.height = size.height 1040 | } 1041 | if(!$$.isNil(size.width)) { 1042 | s.width = size.width 1043 | } 1044 | } 1045 | if(!$$.isNil(s.height) || !$$.isNil(s.width)) { 1046 | postit(null, {type:"resize", config:{client:client}, size:s}) 1047 | } 1048 | }, autogrow:function(client, b, interval) { 1049 | var ival = $$.isNil(interval) ? 300 : interval; 1050 | autog = $$.isNil(b) ? true : b; 1051 | if(autog === false) { 1052 | return 1053 | } 1054 | setTimeout(function() { 1055 | submodules.frame.resize(client); 1056 | submodules.frame.autogrow(client, autog) 1057 | }, ival) 1058 | }} 1059 | }(); 1060 | return{services:services, frame:frame, event:event, callback:callback} 1061 | }(); 1062 | $$.xd.receive(xdCallback, getTargetOrigin); 1063 | return{ctx:submodules.services.ctx, ajax:submodules.services.ajax, token:submodules.services.token, version:submodules.services.version, resize:submodules.frame.resize, size:submodules.frame.size, autogrow:submodules.frame.autogrow, subscribe:submodules.event.subscribe, unsubscribe:submodules.event.unsubscribe, publish:submodules.event.publish, signedrequest:submodules.services.signedrequest, refreshSignedRequest:submodules.services.refreshSignedRequest, repost:submodules.services.repost} 1064 | }(); 1065 | $$.module("Sfdc.canvas.client", module) 1066 | })(Sfdc.canvas); 1067 | -------------------------------------------------------------------------------- /libraries/canvas-starter.js: -------------------------------------------------------------------------------- 1 | var cnv = (function(storage) { 2 | 'use strict'; 3 | return { 4 | 5 | initialize: initialize, 6 | login:login, 7 | logout:logout, 8 | refresh:refresh, 9 | querySalesforce:querySalesforce, 10 | editSalesforce:editSalesforce, 11 | deleteSalesforce:deleteSalesforce, 12 | publish: publish, 13 | navigate:navigate, 14 | }; 15 | 16 | function initialize(callback) { 17 | 18 | //we are logged in and can retrieve and decode our signed request for our calls to salesforce. 19 | Sfdc.canvas.client.refreshSignedRequest(function(data) { 20 | if (data.status === 200) { 21 | var signedRequest = data.payload.response; 22 | var part = signedRequest.split('.')[1]; 23 | //decode and save for this session. 24 | storage.sr = JSON.parse(Sfdc.canvas.decode(part)); 25 | //publish an event to resize the outer frame, now that we're loaded. 26 | publish('cnvstart.resize'); 27 | if (callback) { 28 | callback(storage.sr); 29 | } 30 | } 31 | else if(data.status===0 && callback){ 32 | result = { 33 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 34 | 'message':'No response from Salesforce. Check Internet Connection.', 35 | }; 36 | callback(result); 37 | return; 38 | } 39 | else if (data.payload[0] && data.payload[0].errorCode && callback) { 40 | result = { 41 | 'errorCode':data.payload[0].errorCode, 42 | 'message':"Salesforce Error: " + cleanError(data.payload[0].message), 43 | }; 44 | callback(result); 45 | return; 46 | } 47 | }); 48 | } 49 | 50 | function querySalesforce(query, callback) { 51 | 52 | //clean our query 53 | var newQuery = query.replace(/( |\r|\n)/g, '+'); 54 | 55 | //retrieve our url from the SR object 56 | var url = storage.sr.context.links.restUrl + "query/?q=" + newQuery; 57 | 58 | var sr = storage.sr; 59 | if(!storage.sr.client.oauthToken) { 60 | alert('Error: Access Token Not Available.'); 61 | return; 62 | } 63 | 64 | //Make first call 65 | Sfdc.canvas.client.ajax(url, { 66 | client: storage.sr.client, 67 | success: function(data) { 68 | if (data.status && data.payload) { 69 | process(data); 70 | } 71 | else { 72 | var result = {'payload':[ 73 | { 74 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 75 | 'message':'No response from Salesforce. Check Internet Connection.', 76 | } 77 | ]}; 78 | callback(result); 79 | return; 80 | } 81 | } 82 | }); 83 | 84 | function process(d) { 85 | if (d.done === false) { //additional results initiate next call 86 | var nUrl = d.nextRecordsUrl; 87 | Sfdc.canvas.client.ajax(nUrl, { 88 | client: sr.client, 89 | success: function(data) { 90 | if (data.status && data.payload) { 91 | process(data); 92 | } 93 | else { 94 | var result = [ 95 | { 96 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 97 | 'message':'No response from Salesforce. Check Internet Connection.', 98 | } 99 | ]; 100 | callBack(result); 101 | return; 102 | } 103 | } 104 | }); 105 | } 106 | callback(d); 107 | } 108 | } 109 | 110 | function editSalesforce(object, request, callback) { 111 | 112 | var url; 113 | //if we have an id then remove from the object and save for our PATCH url 114 | var id = false; 115 | if(request.Id) { 116 | id = request.Id; 117 | delete request.Id; 118 | } 119 | //signed request for links and client 120 | var sr = storage.sr; 121 | //Make call 122 | //New Record 123 | if(!id) { 124 | url = sr.context.links.sobjectUrl + object + "/"; 125 | Sfdc.canvas.client.ajax(url, { 126 | client: sr.client, 127 | contentType: "application/json", 128 | method: 'POST', 129 | data: JSON.stringify(request), 130 | success: function(data) { 131 | processPost(data); 132 | } 133 | }); 134 | } 135 | //edit record 136 | else { 137 | url = sr.context.links.sobjectUrl + object + "/" + id + "/"; 138 | Sfdc.canvas.client.ajax(url, { 139 | client: sr.client, 140 | contentType: "application/json", 141 | method: 'PATCH', 142 | data: JSON.stringify(request), 143 | success: function(data) { 144 | processPatch(data); 145 | } 146 | }); 147 | } 148 | 149 | function processPatch(result){ 150 | if(result.status!==0 && !result.payload) { 151 | //no errors 152 | result = { 153 | 'Id': 'id', 154 | }; 155 | } 156 | else if(result.status===0) { 157 | //no internet connection 158 | //make our own error message 159 | result = { 160 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 161 | 'message':'No response from Salesforce. Check Internet Connection.', 162 | }; 163 | } 164 | else { 165 | //salesforce error 166 | } 167 | callback(result); 168 | } 169 | 170 | function processPost(result){ 171 | if(result.status===201 && result.payload) { 172 | result = result.payload; 173 | } 174 | else if(result.status===0) { 175 | //no internet connection 176 | //make our own error message 177 | result = { 178 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 179 | 'message':'No response from Salesforce. Check Internet Connection.', 180 | }; 181 | } 182 | callback(result); 183 | } 184 | } 185 | 186 | function deleteSalesforce(object, id, callback) { 187 | 188 | //sr and base url are in storage. 189 | var sr = storage.sr; 190 | var url = sr.context.links.sobjectUrl + object + "/" + id + "/"; 191 | 192 | //Make call 193 | Sfdc.canvas.client.ajax(url, { 194 | client: sr.client, 195 | contentType: "application/json", 196 | method: 'DELETE', 197 | success: function(data) { 198 | checkResult(data); 199 | } 200 | }); 201 | 202 | //internal callback for constructing return object. 203 | function checkResult(result) { 204 | if (result.status===0) { 205 | //no internet? 206 | result.payload=[ 207 | { 208 | 'errorCode':'No response from Salesforce. Check Internet Connection.', 209 | 'message':'No response from Salesforce. Check Internet Connection.', 210 | } 211 | ]; 212 | } 213 | else if (result.status===204){ 214 | result.payload=[ 215 | { 216 | 'success':true, 217 | } 218 | ]; 219 | } 220 | callback(result.payload); 221 | } 222 | } 223 | 224 | function navigate(id,url,newWindow) { 225 | if(id) { 226 | publish ( "cnvstart.navigate" , { 227 | 'id':id, 228 | 'url' : url, 229 | 'new' : newWindow } ); 230 | } 231 | } 232 | 233 | function publish(event, payload) { 234 | Sfdc.canvas.client.publish(storage.sr.client, { 235 | "name": event, 236 | "payload": payload, 237 | }); 238 | } 239 | 240 | function login() { 241 | console.log('foo'); 242 | loginAction(); 243 | } 244 | 245 | function logout(loginPage) { 246 | //remove the token from the client object and the canvas object 247 | if(storage.sr && storage.sr.client) { 248 | storage.sr.client.oauthToken=''; 249 | } 250 | Sfdc.canvas.oauth.logout(); 251 | if(loginPage) { 252 | window.location.assign('/oauth/sfOauth.html?loginUrl='+encodeURIComponent(storage.sr.context.links.loginUrl)); 253 | } 254 | } 255 | 256 | function refresh(){ 257 | window.location.assign('/index.html#/Overview'); 258 | //Sfdc.canvas.oauth.childWindowUnloadNotification(hash); 259 | } 260 | 261 | //private functions 262 | 263 | function loginAction(consumerData) { 264 | 265 | var url; 266 | //retrieve our key if we don't have it 267 | if(!consumerData) { 268 | getConsumerData(loginAction); 269 | return; 270 | } 271 | 272 | //remove all currebt access tokens 273 | logout(); 274 | 275 | //if loginUrl is a parameter, then we're in the oauth page and can use the parameter to determine our target 276 | var params = decodeURIComponent(location.search); 277 | if(params.indexOf('loginUrl')!==-1){ 278 | url = decodeURIComponent(location.search.split("=")[1]); 279 | } 280 | else { 281 | //determine the url from the signed request 282 | url = storage.sr.context.links.loginUrl; 283 | } 284 | 285 | //if we don't get login.salesforce, then we're in a sandbox. 286 | if(url.indexOf('login.salesforce.com')!==-1) { 287 | url = "https://login.salesforce.com/services/oauth2/authorize"; 288 | } 289 | else { 290 | url = "https://test.salesforce.com/services/oauth2/authorize"; 291 | } 292 | //begin login/authorize process 293 | Sfdc.canvas.oauth.login( 294 | {uri : url, 295 | params: { 296 | response_type : "token", 297 | client_id : consumerData.key, 298 | redirect_uri : encodeURIComponent(consumerData.url) 299 | }}); 300 | } 301 | 302 | function getConsumerData(callback) { 303 | var result; 304 | var request = new XMLHttpRequest(); 305 | request.open('GET','/php/consumerData.php'); 306 | //request.setRequestHeader('Content-Type','text/plain;charset=UTF-8'); 307 | request.onreadystatechange = function(){ 308 | if(request.readyState===4 && request.status===200) { 309 | result=JSON.parse(request.responseText); 310 | callback(result); 311 | } 312 | }; 313 | request.send(null); 314 | } 315 | 316 | }( 317 | //settings session storage for the signed request, etc. 318 | {} 319 | )); 320 | -------------------------------------------------------------------------------- /libraries/ng-route.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.4.4 3 | (c) 2010-2015 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,c,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,d,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(c.isDefined(b&&b.$template)){var b=a.$new(),d=r.current;m=y(b,function(b){g.enter(b,null,m||f).then(function(){!c.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=d.scope=b;l.$emit("$viewContentLoaded"); 7 | l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(c,h,g){return{restrict:"ECA",priority:-400,link:function(a,f){var b=g.current,d=b.locals;f.html(d.$template);var y=c(f.contents());b.controller&&(d.$scope=a,d=h(b.controller,d),b.controllerAs&&(a[b.controllerAs]=d),f.data("$ngControllerController",d),f.children().data("$ngControllerController",d));y(a)}}}p=c.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return c.extend(Object.create(a), 8 | f)}function h(a,c){var b=c.caseInsensitiveMatch,d={originalPath:a,regexp:a},g=d.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,c,b,d){a="?"===d?d:null;d="*"===d?d:null;g.push({name:b,optional:!!a});c=c||"";return""+(a?"":c)+"(?:"+(a?c:"")+(d&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=new RegExp("^"+a+"$",b?"i":"");return d}var g={};this.when=function(a,f){var b=c.copy(f);c.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | c.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=c.extend(b,a&&h(a,b));if(a){var d="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[d]=c.extend({redirectTo:a},h(d,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,d,h,p,x){function l(b){var e=s.current; 10 | (v=(n=k())&&e&&n.$$route===e.$$route&&c.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,c.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(c.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),d.when(e).then(function(){if(e){var a= 11 | c.extend({},e.resolve),b,f;c.forEach(a,function(b,e){a[e]=c.isString(b)?h.get(b):h.invoke(b,null,null,e)});c.isDefined(b=e.template)?c.isFunction(b)&&(b=b(e.params)):c.isDefined(f=e.templateUrl)&&(c.isFunction(f)&&(f=f(e.params)),c.isDefined(f)&&(e.loadedTemplateUrl=x.valueOf(f),b=p(f)));c.isDefined(b)&&(a.$template=b);return d.all(a)}}).then(function(f){e==s.current&&(e&&(e.locals=f,c.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function k(){var a,b;c.forEach(g,function(d,g){var q;if(q=!b){var h=f.path();q=d.keys;var l={};if(d.regexp)if(h=d.regexp.exec(h)){for(var k=1,m=h.length;k 2 | 3 | 4 | Callback Refresh Window 5 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /oauth/sfOauth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | 26 |
27 |

User Authentication Required

28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /packageComponents/Canvas_Starter.vf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packageComponents/apexClasses/AlternateResource.apex: -------------------------------------------------------------------------------- 1 | global with sharing class AlternateResource { 2 | public String getResourcePath(){ 3 | String v = cnvstart__alternateJavascript__c.getInstance().cnvstart__Resource_Name__c; 4 | String pre = ''; 5 | if (v==null){ 6 | v = 'canvasStatic'; 7 | pre = ''; 8 | } 9 | Long ts = getResourceStamp(v); 10 | if (ts==null){ 11 | v = 'canvasStatic'; 12 | pre = ''; 13 | ts = getResourceStamp(v); 14 | } 15 | return '/resource/' + ts + '/'+ pre + v; 16 | } 17 | @TestVisible 18 | private Long getResourceStamp(String r){ 19 | if(r==null){ 20 | return null; 21 | } 22 | else{ 23 | List resourceList= [SELECT SystemModStamp FROM StaticResource WHERE Name = :r]; 24 | return resourceList[0].systemModStamp.getTime(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packageComponents/apexClasses/AlternateResourceTest.apex: -------------------------------------------------------------------------------- 1 | @istest 2 | class AlternateResourceTest { 3 | @isTest static void testController() { 4 | Long ts, tss; 5 | AlternateResource jsr = new AlternateResource(); 6 | String v = cnvstart__alternateJavascript__c.getInstance().cnvstart__Resource_Name__c; 7 | String pre = ''; 8 | System.debug(v); 9 | if (v==null){ //if no alternate resource specified, use stock resource 10 | tss = jsr.getResourceStamp(v); 11 | System.assertEquals(tss, null); 12 | v = 'canvasStatic'; 13 | pre = ''; 14 | } 15 | List resourceList= [SELECT SystemModStamp FROM StaticResource WHERE Name = :v]; 16 | ts = resourceList[0].systemModStamp.getTime(); 17 | System.debug(ts); 18 | tss = jsr.getResourceStamp(v); 19 | System.debug(tss); 20 | System.assertEquals(ts, tss); 21 | if (resourceList[0]==null){ //if specified resource is not found then fall back to stock values 22 | v = 'canvasStatic'; 23 | pre = ''; 24 | resourceList= [SELECT SystemModStamp FROM StaticResource WHERE Name = :v]; 25 | System.debug(resourceList); 26 | } 27 | ts = resourceList[0].systemModStamp.getTime(); 28 | System.debug(ts); 29 | tss = jsr.getResourceStamp(v); 30 | System.debug(tss); 31 | System.assertEquals(ts, tss); 32 | v = '/resource/' + ts + '/' + pre + v; 33 | System.debug(v); 34 | String tv = jsr.getResourcePath(); 35 | System.assertEquals(v, tv); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packageComponents/staticresources/canvas-static.js: -------------------------------------------------------------------------------- 1 | //local javascript VF Page. 2 | 3 | /* 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) 2016 SeedCode 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | */ 28 | 29 | var cnv = (function() { 30 | "use strict"; 31 | 32 | var config; 33 | //Get the initial window height so this works correctly on mobile. 34 | var initialWindowHeight = window.innerHeight; 35 | 36 | //Public methods 37 | return { 38 | subscribe: subscribe, 39 | init: init, 40 | resize: resize, 41 | publish: publish, 42 | }; 43 | 44 | //publish to canvas 45 | function publish(eventName, payload) { 46 | Sfdc.canvas.controller.publish( 47 | { 48 | name : eventName, 49 | payload : payload, 50 | } 51 | ); 52 | } 53 | 54 | 55 | //Use the cnv.publish(,) to call these events from your canvas app. 56 | function subscribe() { 57 | Sfdc.canvas.controller.subscribe( 58 | [ 59 | //this allows us to publish a resize event from within dayback 60 | { 61 | "name": "cnvstart.resize", 62 | "onData": function(e) { 63 | resize(e); 64 | } 65 | }, 66 | { 67 | "name": "cnvstart.navigate", 68 | "onData": navigate, 69 | }, 70 | 71 | //############################################################# 72 | //############################################################# 73 | //############################################################# 74 | //add your event subscription events here separated by commas 75 | //Use a unique name with the dbk prefix 76 | //follow the syntax from the above events 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | //############################################################# 103 | ] //end array of subscriptions 104 | ); 105 | } 106 | 107 | 108 | 109 | 110 | //!!You shouldn't need to edit below this line!! 111 | 112 | function init(initialConfig) { 113 | 114 | //Assign config to global var 115 | config = initialConfig; 116 | 117 | //Resize our view so the iframes are sized correctly 118 | resize(); 119 | 120 | //Add resize event listener 121 | window.addEventListener("resize", function() { 122 | resize(); 123 | }); 124 | } 125 | 126 | //function for resizing canvas in vf page. 127 | //Called after load and resize from vf page. 128 | 129 | function resize(e) { 130 | var height, target; 131 | 132 | //If we are on mobile web we don't want to run 133 | //resize as there is a salesforce bug currently 134 | if (isMobileWeb()) { 135 | //Mobile web site 136 | height = getMobileHeight(); 137 | } 138 | else if (isMobileOne()) { 139 | //Mobile app 140 | height = getMobileHeight(); 141 | } 142 | else if (isLightningDesktop()) { 143 | //Lightning Desktop 144 | height = window.innerHeight; 145 | } 146 | else { 147 | //Desktop 148 | height = getHeight(); 149 | } 150 | 151 | target = { 152 | "canvas": "cnvstart" 153 | }; 154 | 155 | Sfdc.canvas.controller.resize({ 156 | "height": height + "px" 157 | }, target); 158 | 159 | //Returns the height we would like to set the iframe height to 160 | function getHeight() { 161 | var headerElement, footerElement, bodyElement, bodyOffset, height; 162 | try { 163 | headerElement = document.querySelector(".bPageHeader"); 164 | footerElement = document.querySelector(".bPageFooter"); 165 | bodyElement = document.querySelector(".bodyDiv"); 166 | bodyOffset = bodyElement.offsetHeight - bodyElement.clientHeight; 167 | 168 | height = window.innerHeight - headerElement.offsetHeight - footerElement.offsetHeight - bodyOffset; 169 | } 170 | catch(error) { 171 | height = window.innerHeight - 132; 172 | } 173 | return height; 174 | } 175 | 176 | //Returns the height we would like to set the iframe height to on mobile devices 177 | function getMobileHeight() { 178 | var height = initialWindowHeight; 179 | return height; 180 | } 181 | } 182 | 183 | //Functions to determine what platform we are running on 184 | function isClassicDesktop() { 185 | var theme = config.theme; 186 | return theme === 'Theme1' || theme === 'Theme2' || theme === 'Theme3'; 187 | } 188 | 189 | function isLightningDesktop() { 190 | var theme = config.theme; 191 | return theme === 'Theme4d'; 192 | } 193 | 194 | function isMobileOne() { 195 | var theme = config.theme; 196 | return theme === 'Theme4t'; 197 | } 198 | 199 | //Check if we are on the mobile lightning sites 200 | function isMobileWeb() { 201 | var url = window.location.href; 202 | var matchString = "Host=web"; 203 | return url.indexOf(matchString) > -1; 204 | } 205 | 206 | function navigate (e) { 207 | var newTab = e.new; 208 | var url = e.url; 209 | var id = e.id; 210 | var view = e.view; 211 | if(!url && id) { 212 | url="/"+id; 213 | } 214 | if( ( isLightningDesktop() || isMobileOne() ) && id ){ 215 | if(view){ 216 | sforce.one.navigateToSObject(id,view); 217 | } 218 | else { 219 | sforce.one.navigateToSObject(id); 220 | } 221 | } 222 | else if (newTab && url) { 223 | window.open(url); 224 | } 225 | else if(url) { 226 | window.location.assign(url); 227 | } 228 | } 229 | 230 | }()); 231 | -------------------------------------------------------------------------------- /packageComponents/staticresources/styles/lightning.css: -------------------------------------------------------------------------------- 1 | body.sfdcBody { 2 | padding: 0 !important; 3 | } 4 | 5 | iframe { 6 | height: 100vh; 7 | } 8 | -------------------------------------------------------------------------------- /packageComponents/staticresources/styles/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .bodyDiv { 6 | overflow: auto; 7 | } 8 | 9 | .bodyDiv table.outerNoSidebar { 10 | padding: 0; 11 | } 12 | 13 | .bodyDiv td.noSidebarCell { 14 | padding: 0; 15 | } 16 | -------------------------------------------------------------------------------- /php/canvas.php: -------------------------------------------------------------------------------- 1 | '; 11 | $sep = strpos($signedRequest, '.'); 12 | $encodedSig = substr($signedRequest, 0, $sep); 13 | $encodedEnv = substr($signedRequest, $sep + 1); 14 | $calcedSig = base64_encode(hash_hmac('sha256', $encodedEnv, $consumer_secret, true)); 15 | if ($calcedSig === $encodedSig) { 16 | // signatures match, proceed to app. 17 | header('Location: ../index.html#/Overview'); 18 | } 19 | else { 20 | // signatures fo not match 21 | echo 'Error: Signed Request Failed. Is the app in Canvas?'; 22 | } 23 | } 24 | else if(isset($_GET['_sfdc_canvas_auth'])){ 25 | //received GET instead of signed post means that users must self authorize (org setting) 26 | //proceed to Oauth page. 27 | $loginUrl = urlencode($_GET['loginUrl']); 28 | header('Location: ../oauth/sfOauth.html?loginUrl='.$loginUrl); 29 | } 30 | else{ 31 | //No POST or GET, not opened in Salesforce. 32 | echo 'Error: Signed Request not found.'; 33 | } 34 | ?> 35 | -------------------------------------------------------------------------------- /php/consumerData.php: -------------------------------------------------------------------------------- 1 | '; 3 | $callBackURL = ''; 4 | 5 | echo '{"key":"'.$key.'","url":"'.$callBackURL.'"}' 6 | ?> 7 | --------------------------------------------------------------------------------