Friday, May 22, 2015

SalesForce Packaging for AppExchange

SalesForce support different package type so you can distribute your app to your customers. Here is summary of what we found:

Scenario Packaging
Unamanged
with/without namespace
Managed Beta Managed Released
Description
Unmanaged packages are typically used to distribute open-source projects or application templates to provide developers with the basic building blocks for an application. Once the components are installed from an unmanaged package, the components can be edited in the organization they are installed in.
More Information
Use to test and validate this package internally and with selected customers before release.
  • Note: this type of package can only be installed in Developer Edition, sandbox organizations, or testing organizations for registered partners
  • For a given managed package, for each version we can decide it to have it beta or released. Typically there would be multiple beta versions in between each release.
Use when you are ready to publish to Force.com AppExchange

More Information
Can Customer See your Source Code?
Yes
No
Namespace
Not required.
  • Even if namespace is added to Org which is publishing the App, it doesn't add it to customer.
  • From Scenario which were tested, it doesn't make any difference with namespace.
Required
Version Upgrade
Not supported.
  • Customer will need to uninstall and install the package
  • Customer will not be able to just reinstall (overlay) on top of old version
  • If customer code has reference to App code, then they will need to remove them before uninstall
Not supported.
  • Customer will need to uninstall and install the package
  • Customer will not be able to just reinstall (overlay) on top of old version
  • If customer code has reference to App code, then they will need to remove them before uninstall
Supported.
  • Customer will be able to just reinstall (overlay) on top of old version
  • If customer code has reference to App code, then they will not be required to change that.
  • "Push Upgrade Feature" feature is available to push the upgrade to customers More Information
Changes to the Code From App Publisher Org (APO)
Allowed.
  • Publisher Org (APO) have no limitation. APO can make any code change.
Restricted.
  • Publisher Org (APO) would have limitation on code changes
  • No signature changes on global methods allowed
  • Can not delete global classes
Customer Name Conflict
Fails.
  • If customer has same artifact name as the artifact name used in App, installation of package would fail.
Works.
  • Namespace are unique across SFDC, and App artifacts are in different namespace so no conflict occurs.
Customer Reference to App Code
No restriction.
  • Customer will be able to use, modify or delete the artifacts.
  • Reference to packaged code would be similar to any other code. e.g. ClassName.methodName
Restricted.
  • Customer will be only be able to use Class and Method which are declared "global" in Package.
  • Package publisher will need to define class and all the methods, and WS api to global if customer needs to consume them
  • Reference to packaged code would need to include namespace. e.g. Namespace.ClassName.methodName
Uninstall
Supported
  • Package can be uninstalled from Setup Console
  • If Customer has reference to Package artifacts, those must to be removed/de-referenced before uninstall
Supported
  • Package can be uninstalled from Setup Console
  • If Customer has reference to Package artifacts, those must to be removed/de-referenced before uninstall
Intra Upgrade
  • Unmanaged to Managed
  • Managed Beta to Managed Released
Not Applicable Not Supported
  • Unmanaged to Managed Beta
  • Customer will need to uninstall Unmanaged Package and install Managed Beta Package manually
Not Supported
  • Managed Beta to Managed Released
  • Customer will need to uninstall Managed Beta and install Managed Released Package manually(*)
* : Need to test further if there is any setting which might allow this.

Friday, May 15, 2015

iFrame content via JQuery

We can access the content of iFrame using JQuery using $("#iframe1").contents().find("html").html(), however it works only if iframe belongs to the same domain as parent container.

E.g. for below HTML, iframe1 and iframe3 is pointing to external URL and iframe2 is pointing the page with same domain:

1:  <html>  
2:       <head>  
3:            <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>  
4:       </head>  
5:       <body>  
6:            <button type="button" id="iframecontent" onclick="iframecontent_click()">IFrame Content</button>  
7:            <iframe id='iframe1' frameborder="0" height="300pt" scrolling="auto" src="http://testserver.com?func=ll&amp;objId=92043&amp;objAction=browse&amp;saptheme=default&amp;wanttabs=0" style="background-color: transparent; border: 0px none transparent;padding: 0px" width="100%"></iframe>  
8:            <iframe id='iframe2' frameborder="0" height="300pt" scrolling="auto" src="test2.html" style="background-color: transparent; border: 0px none transparent;padding: 0px" width="100%"></iframe>      
9:            <iframe id='iframe3' frameborder="0" height="300pt" scrolling="auto" src="http://www.cnn.com" style="background-color: transparent; border: 0px none transparent;padding: 0px" width="100%"></iframe>      
10:       </body>  
11:       <script>  
12:            function iframecontent_click() {  
13:                 console.log('iframe1 content : ' + $("#iframe1").contents().find("html").html());  
14:                 console.log('iframe2 content : ' + $("#iframe2").contents().find("html").html());  
15:                 console.log('iframe3 content : ' + $("#iframe3").contents().find("html").html());  
16:            }  
17:       </script>  
18:  </html>  

Here via Jquery iframe1 and iframe3 content shows up as "undefined". It works fine for iFrame2.


In SalesForce, if you include visual force page inside the standard page, it is included inside the iFrame and Salesforce includes visual force page with different domain iFrame, as below. Hence in parent container JQuery would not be able to access content of child visual force page iframe.



Thursday, May 14, 2015

SalesForce LightBox/Modal dialog on Standard Page

In visual force page, it is quite easy to implement light box using any of the jquery plugin or even with just use of css. However when we have standard page and want to open light box upon button/link click, it wasn't straightforward:

Option 1: Lightbox inside Visual Force
Include visual force page inside standard page and put light box logic inside visual force page. 
This option doesn't work well, as when we include the visualforce page in standard page, it includes that page inside iframe. Hence if you open the light box from visualforce page, it will stay inside that iFrame.

Option 2 : Modal Dialog
When we create custom button to open the visual force page, it always opens in new page, to open as modal dialg, we can use below java script:


Javascript
In below script, it opens up the visual force page using modal dialog. It updates the parent page's onFocus and onClick event so it is disabled when the child window is open.

1:  var popupWindow;  
2:  var previousOnFocus;  
3:  var previousOnClick;  
4:    
5:  function parent_disable() {   
6:   if(popupWindow && !popupWindow.closed) {  
7:     popupWindow.focus();  
8:   } else if(popupWindow && popupWindow.closed) {  
9:     window.onfocus = previousOnFocus;  
10:     window.onclick = previousOnClick;  
11:   }  
12:  }       
13:    
14:  function openModel(pageURL) {  
15:    var w = (window.innerWidth * 80 / 100);  
16:    var h = (window.innerHeight * 80 / 100);  
17:    previousOnFocus = window.onFocus;  
18:    previousOnClick = window.onClick;  
19:    window.onfocus = parent_disable;  
20:    window.onclick = parent_disable;  
21:    var left = (screen.width/2)-(w/2);  
22:    var top = (screen.height/2)-(h/2);  
23:    popupWindow = window.open(pageURL,'Workspace',   
24:    'height='+h+',width='+w+',top='+top+',left='+left+',toolbar=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=yes,modal=yes');  
25:    popupWindow.focus();  
26:  }  
27:    
28:  openModel('/apex/WorkspaceIF?id={!Account.Id}');  




Option 3 : LightBox
In this option we will use javascript to insert lighbox on the fly. For this option we had to get JQuery loaded which helps with DOM manipulation (and also we had to get some content from server so had to make rest calls).



in JavaScript block, load JQuery, JQueryUI and JQueryCSS. Once JQuery is loaded, the lightbox function is called (as mentioned in line 27).

1:  lightBox_click();  
2:    
3:  function lightBox_click() {  
4:   xxLoadJQuery();  
5:  }  
6:    
7:  function xxLoadScript(url, callback) {   
8:   var head = document.getElementsByTagName('head')[0];   
9:   var script = document.createElement('script');   
10:   script.type = 'text/javascript';   
11:   script.src = url;   
12:   script.onreadystatechange = callback;   
13:   script.onload = callback;   
14:   head.appendChild(script);   
15:  }  
16:    
17:  function xxLoadJQuery() {  
18:   xxLoadScript("https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js", xxLoadJQueryUI);  
19:  }  
20:    
21:  function xxLoadJQueryUI() {  
22:   xxLoadScript("https://code.jquery.com/ui/1.11.3/jquery-ui.min.js", xxLoadJQueryCSS);   
23:  }  
24:    
25:  function xxLoadJQueryCSS() {   
26:   $("&lt;link/>", { rel: "stylesheet", type: "text/css", href: "https://code.jquery.com/ui/1.11.3/themes/smoothness/jquery-ui.css"}).appendTo("head");   
27:   xxLightBox();  
28:  }   
29:    

Now, in xxLightBox function, required div and css are inserted to enable the lightbox.

1:  function xxLightBox() {  
2:     $("<style>.modalDialog {position: fixed;font-family: Arial, Helvetica, sans-serif;top: 0;right: 0;bottom: 0;left: 0;background: rgba(0,0,0,0.8);z-index: 99999;opacity:0;-webkit-transition: opacity 400ms ease-in;-moz-transition: opacity 400ms ease-in;transition: opacity 400ms ease-in;pointer-events: none;} .modalDialog:target {opacity:1;pointer-events: auto;} .modalDialog > div {width: 95%;position: relative;margin: 1% auto;padding: 1px 1px 1px px;border-radius: 5px;background: #fff;background: -moz-linear-gradient(#fff, #999);background: -webkit-linear-gradient(#fff, #999);background: -o-linear-gradient(#fff, #999);}.close {background: #606061;color: #FFFFFF;line-height: 25px;position: absolute;right: -12px;text-align: center;top: -10px;width: 24px;text-decoration: none;font-weight: bold;-webkit-border-radius: 12px;-moz-border-radius: 12px;border-radius: 12px;-moz-box-shadow: 1px 1px 3px #000;-webkit-box-shadow: 1px 1px 3px #000; box-shadow: 1px 1px 3px #000;} .close:hover { background: #00d9ff; } </style>").appendTo("head");  
3:     var height = ( $( document ).height() * 90 / 100 );  
4:     var iFrameURL = xxGetIFramURL();  
5:     var lighboxDiv = '<a id="openModelLink" href="#openModal"></a><div id="openModal" class="modalDialog"><div><a id="closeModelLink" href="#close" class="close">X</a><iframe frameborder="0" height="' + height + '" scrolling="auto" src="' + iFrameURL + '" style="background-color: transparent; border: 0px none transparent;padding: 0px" width="100%"></iframe></div></div>';  
6:     if( iFrameURL == null || iFrameURL == '' ) {  
7:        lighboxDiv = '<a id="openModelLink" href="#openModal"></a><div id="openModal" class="modalDialog"><div><a id="closeModelLink" href="#close" class="close">X</a>Workspace does not exist.</div></div>';  
8:     }   
9:     $( "body" ).append(lighboxDiv);  
10:    document.getElementById("openModelLink").click();  
11:  }  
12:    


Extra note here (not needed for this example), we are displaying iFrame inside lightbox and I had to get iFrame URL from server, hence I had to make Rest call using JQuery as below (also explained in detail here)

1:  function xxGetIFramURL() {  
2:     var iFrameURL = '';  
3:     $.ajax({  
4:        url:  '/services/apexrest/WorkspaceService?salesForceId={!Account.Id}',  
5:        beforeSend: function(xhr) {  
6:           xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}');  
7:        },  
8:        success: function(result) {  
9:           iFrameURL = result;  
10:       },  
11:       async:  false  
12:    });  
13:    return iFrameURL;  
14:  }  





SalesForce rest api on Standard page

In Visual Force, it is very straightforward to use server side logic (Apex Classes) using Controller. However, when working on standard page - e.g. custom button java script, we didn't have access to the controller. E.g. below we have access to object and JavaScript block. SalesForce executes this java script using JavaScript's eval function.



If we need data from server side, it is a lot easier to get that using SalesForce Rest API. We also don't need to worry about security as we can get OAUTH Bearer token using {!$Api.Session_ID}').


  • Create Apex Class and expose as rest service as below


@RestResource(urlMapping='/MyRestService/*')
global with sharing class MyRestServiceImpl {

    @HttpGet
    global static String getIFrameURL() {
        String salesForceId = RestContext.request.params.get('salesForceId');
        return 'http://www.google.com?id='+salesForceId;
    }
}


  • Test is out using workbench.developerforce.com using URL /services/apexrest/MyRestService?salesForceId=1234





  • Below is Java Script code to call this service


First load the Jquery using java script as below, it loads jquery, jquery ui and jquery-ui.css

1:  lightBox_click();   
2:    
3:  function lightBox_click() {   
4:   xxLoadJQuery();   
5:  }   
6:    
7:  function xxLoadScript(url, callback) {   
8:   var head = document.getElementsByTagName('head')[0];   
9:   var script = document.createElement('script');   
10:   script.type = 'text/javascript';   
11:   script.src = url;   
12:   script.onreadystatechange = callback;   
13:   script.onload = callback;   
14:   head.appendChild(script);   
15:  }   
16:    
17:  function xxLoadJQuery() {   
18:   xxLoadScript("https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js", xxLoadJQueryUI);   
19:  }   
20:    
21:  function xxLoadJQueryUI() {   
22:   xxLoadScript("https://code.jquery.com/ui/1.11.3/jquery-ui.min.js", xxLoadJQueryCSS);   
23:  }   
24:    
25:  function xxLoadJQueryCSS() {   
26:   $("&lt;link/>", { rel: "stylesheet", type: "text/css", href: "https://code.jquery.com/ui/1.11.3/themes/smoothness/jquery-ui.css"}).appendTo("head");   
27:   xxLightBox();   
28:  }   


Make a call to Rest Service using Jquery. Line number 11 passes the authorization token.

1:  function xxLightBox() {   
2:   var iFrameURL = xxGetIFramURL();   
3:   alert(' iFrameURL ' + iFrameURL );  
4:  }   
5:    
6:  function xxGetIFramURL() {   
7:   var iFrameURL = '';   
8:   $.ajax({   
9:    url: '/services/apexrest/MyRestService?salesForceId={!Account.Id}',   
10:    beforeSend: function(xhr) {   
11:     xhr.setRequestHeader('Authorization', 'Bearer {!$Api.Session_ID}');   
12:    },   
13:    success: function(result) {   
14:     iFrameURL = result;   
15:    },   
16:    async: false   
17:   });   
18:   return iFrameURL;   
19:  }  



  • Test using putting this button on layout: