Monday, May 19, 2014


Well this weekend something surprising happened, Visualstrap crossed 1000 install limit (inlcuding appexchange & from my blog). I am really excited and happy that people are finding this small little package useful. Thanks for all the support !

So to make this package more delightful and easy to learn, I have redesigned the Visualstrap site to include all the documents and instruction at one place.



This site itself is an example of how you can create a good custom ui inside salesforce. I am still working on making the site better and putting all the documentation at one place. Thanks for all the support and love!

Saturday, May 17, 2014


This is quick little post on how to create a force.com site/page to access Salesforce even if you don't have the access codes and to some extent to create your own custom login screen. Generally helpful when same login is shared by multiple user.

There are already apps like Trapdoor for mac that lets you do the same, but this one just an cloud version of the same and of course you can build one for yourself!

What you need to make this work ?

  • Username and Password
  • The security token

How this works ?

  • The username  & password entered are used to make a SOAP call to Salesforce using the partner Endpoint
  • The response is parsed to retrieve the SessionId and Salesforce Server URL
  • Using the ServerURL and SessionId the user is redirected to "frontdoor.jsp" page of Salesforce which lets user log into Salesforce using session Id.

This page can be hosted in any org and not necessarily in the org you are trying to log in

Code 

Page

The Page uses Visualstrap to create a custom login screen. Same can be installed from here . And you can always copy the page code to create your own custom login screen for salesforce :)

<apex:page controller="SalesforceLogin_Con" showHeader="false" docType="html-5.0">
    <style>  
     body{  
         font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;  
         background: #344A5F;
         
     }    
     
     .logo{
         text-align:center;
         height:44px;
         width:44px;
         margin: auto;
         background-color: #2A94D6;
         padding-top:8px;
         border-radius:8px;
         font-size:200%;
         color:white;
         display:inline-block;
         font-weight:bold;
     }
     
     .logoTxt{
         margin-left:10px;
         font-weight:bold;
         color:#8199af;
         display:inline-block;
     }
     
     .spcl{
         color:#1abc9c !important;
         padding-bottom:-20px;
         margin-bottom:0px !important;
     }
     
     label{
         font-size:130%;
     }
     
     .msgIcon{display:none}
     .messageText h4{display:none}
   </style>
   
    <vs:importvisualstrap />
    <apex:form >
        <vs:visualstrapblock >
            <c:navbar brand="Blogforce9" logo="http://blogforce9dev-developer-edition.ap1.force.com/resource/1385742741000/BlogForce9__BlogForce9WhiteLogo" inverse="true" type="fixed-top">
            <a href="http://blogforce9.blogspot.in/" target="_blank" class="btn btn-primary btn-danger"><vs:glyph icon="home"/> Home</a>
        </c:navbar>
           <div class="container">
                <apex:outputPanel layout="block" styleClass="row">
                    <div class="col-md-6 col-md-offset-3">
                        <div class="jumbotron spcl">
                           <vs:panel >
                               <div style="margin-bottom:10px">
                                   <div class="logo">S</div>
                                   
                                   <div class="logoTxt">
                                       <div style="font-size:150%;">Salesforce Login</div>
                                       <small style="font-weight:normal">Login to Salesforce using Security token to avoid Access Code emails</small>
                                   </div>
                               </div>
                           </vs:panel>
                            <vs:panel >
                                <apex:pageMessages ></apex:pageMessages>
                                <vs:formblock alignment="horizontal" style="margin-top:5px;padding:10px">
                                    <vs:formgroup >
                                        <apex:outputLabel >Username</apex:outputLabel>
                                        <apex:inputText value="{!Username}" styleClass="form-control" required="true"/>
                                    </vs:formgroup> 
                                    <vs:formgroup >
                                        <apex:outputLabel >Password + Security Token</apex:outputLabel>
                                        <apex:inputSecret value="{!Password}" styleClass="form-control" required="true"/>
                                    </vs:formgroup>
                                    <vs:formgroup >
                                        <apex:outputLabel >Domain</apex:outputLabel>
                                        <apex:selectList value="{!domain}" styleClass="form-control" size="1">
                                            <apex:selectOption itemValue="login" itemLabel="login"></apex:selectOption>
                                            <apex:selectOption itemValue="test" itemLabel="test"></apex:selectOption>
                                        </apex:selectList>                                        
                                    </vs:formgroup>
                                    <vs:formgroup >
                                        <apex:commandButton value="Login" action="{!doLogin}" styleClass="btn btn-success btn-md pull-right" style="width:200px"/>
                                    </vs:formgroup>
                                    <small class="text-info">*Passwords are not saved by this page</small>
                                </vs:formblock>
                            </vs:panel>                            
                        </div>
                    </div>
                </apex:outputPanel>
            </div>
            
            <c:navbar brand="Blogforce9" inverse="true" type="fixed-bottom" layout="none" >              
                <div class="text-muted" style="margin:10px;font-size:130%;text-align:Center" layout="block">  
                    Site built with  <a href="http://blogforce9dev-developer-edition.ap1.force.com/ProjectDetail?id=a0290000009MI61" target="_blank" class="btn btn-sm btn-danger"><vs:glyph icon="heart"/> VisualStrap</a> 
                </div>  
            </c:navbar>
        </vs:visualstrapblock>
    </apex:form>
</apex:page>

Controller

public class SalesforceLogin_Con {
    
    public String username{get;set;}
    public String password{get;set;}
    public String domain{get;set;}

    public Pagereference doLogin(){
        Pagereference loginReference;
        try{
            
            String loginRes = sendLoginRequest(username,password,domain);
            if(!String.isBlank(loginRes) && !loginRes.containsIgnoreCase('INVALID_LOGIN')){
                Dom.Document loginDoc = new Dom.Document();
                loginDoc.load(loginRes);
                Dom.XmlNode resultElmt = loginDoc.getRootElement()
                  .getChildElement('Body','http://schemas.xmlsoap.org/soap/envelope/')
                  .getChildElement('loginResponse','urn:partner.soap.sforce.com')
                  .getChildElement('result','urn:partner.soap.sforce.com');
                /*Extract the session Id and Server url*/
                String serverurl = resultElmt.getChildElement('serverUrl','urn:partner.soap.sforce.com').getText().split('/services')[0];
                String sessionId = resultElmt.getChildElement('sessionId','urn:partner.soap.sforce.com').getText();
                /*Use frontdoor.jsp to login to salesforce*/
                loginReference =  new Pagereference(serverurl+'/secur/frontdoor.jsp?sid='+sessionId);
            }
            else{
                Apexpages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'Please check your Username and Password.'));
            }
       }
       catch(Exception ex){
            Apexpages.addMessages(ex);
       }
        
        return loginReference;
    }
    
    /*Method to send login request using using SOAP*/
    private static String sendLoginRequest(String un,String pw,String domain){
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://' + domain + '.salesforce.com/services/Soap/u/30.0');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'text/xml;charset=UTF-8');
        request.setHeader('SOAPAction', '""');
        request.setBody('<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"><Header/><Body><login xmlns="urn:partner.soap.sforce.com"><username>' + un + '</username><password>' + pw + '</password></login></Body></Envelope>');
        Http h = new Http();
        HttpResponse res = h.send(request);
        return res.getBody();
    } 
}
This Code can further be Reused for
  • Generate SessionId to Call Salesforce REST Endpoints
  • Salesforce to Salesforce custom Integration
  • This page can be hosted in Force.com site as a Custom/Branded Salesforce Login

Monday, May 12, 2014


For a while I have been wondering how to build a Resumable form , which resumes the last tucked in values somehow. Earlier attempts of mine included storing the data somewhere in a Sobject so that they can be fetched back, but that seemed very cumbersome!

The other day I came across a JS plugin called GarlicJS and it looked very promising, and provided a better way to solve the above problem of retaining values. GarlicJS stores the values entered in the fields in local storage and whenever there is a page refresh it loads the values from the local storage. I took the JS plugin and wrapped it inside a VF component to create what I am calling as "VFResumable"


VFResumable : What it is ?


VFResumable is a Visualforce component based on GarlicJS which helps to resume previously entered values. VFResumable provides some control over GarlicJS using wrapper methods and attributes.


Features

  • Resumes the previously entered values, even in case of full Page Refresh
  • Exposes enable and clear JS method to clear/enable resumable
  • Works with any VF page
  • GarlicJS automatically clears the stored values on form submit, in case you are using AJAX you may want to use the helper functions clearVFResumable and initVFResumable

Attributes & JSFunctions

  • Attributes
    • formid : A comma seperated list of Ids of target apex form
    • importjquery : Set this false incase you are already using Jquery in your pages
    • enable : Set true if you want to enable the VFresumable as soon as page loads, set false to disable this, when false you may have to use js handler to enable VFResumable
  • JSFunctions
    • initVFResumable : Call this JS function if you want to explicitly enable the VFResumable.
    • clearVFResumable : Call this JS function if you want to clear the stored values. Generally should be called from oncomplete of a button/action function (assuming you want to store values if the record was not saved)

Usage & Code


<apex:page standardController="Lead">
    <apex:sectionHeader title="VFResumable" subtitle="Restore previous values using VFResumable"/>
    
    <apex:form id="myForm" >       
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <!-- Button to clear the resumable -->
                <apex:commandButton value="Destroy" onclick="clearVFResumable();return false;"/>
            </apex:pageBlockButtons>
            <apex:pageBlockSection >
                <apex:inputField value="{!Lead.FirstName}"/>
                <apex:inputField value="{!Lead.LastName}"/>
                <apex:inputField value="{!Lead.MobilePhone}"/>
            </apex:pageBlockSection>
        </apex:pageBlock>
    </apex:form>
    <!-- actual vf component -->
    <c:VFResumable formids="myForm"/>
</apex:page>

Saturday, May 3, 2014


Working with VisualForce and JS always fascinates me, I am constantly trying to do things a bit differently, trying to use more JS on my pages. And here is something from the same stable.

PDFAttacher - What it is ?


Its a VF page that is capable to pull PDF from another VF page (rendered as PDF) and attach the same to a record as an attachment.

Whats so special about this ?


Well pulling PDF from a VF source and attaching to a record can easily be done using apex, but PDFAttacher on the other side doesn't uses apex and solely depends upon JS and AJAX Toolkit.
  • No Apex Solution : Makes it possible to run in orgs that doesn't have access to APEX but they have access to API and VF. Its really very helpful for those orgs as it gives you ability to easily attach a Dynamically generated PDF.
  • Configurable & Reusable : Page run on some of the easily configurable parameters, that can be changed to attach attachments to different Parent Records and even Objects.

URL Parameters


  • pdfSrcUrl : VF page url from where the PDF needs to be extracted
  • pid : Parent record Id where the attachment needs to be attached
  • pdfName : Filename to be used to save the PDF
  • retUrl : URL where browser needs to be redirected on successful generation of PDF

Other Configurations 


  • Make sure Developer Footer is off.
  • Make sure the Source VF page is rendered as PDF.
  • API access is needed


How to use ?


Create a Button on a Object and set the URL as described in the "URL Parameters" section.



Sample URL 



https://blogforce9.ap1.visual.force.com/apex/PDFAttacher?pdfSrcUrl=https://blogforce9.ap1.visual.force.com/apex/TestPageB&pid={!Account.Id}&pdfName=PDFAttacherTest.pdf&retUrl=/{!Account.Id}


  • Source PDF is : https://blogforce9.ap1.visual.force.com/apex/TestPageB
  • Parent Record Id : {!Account.Id}
  • Generated File Name : PDFAttacherTest.pdf
  • Return URL : /{!Account.Id}


Code 


<apex:page >  
   <apex:includeScript value="../../soap/ajax/29.0/connection.js" />  
   <script>  
     /*Mehtod to upload attachment to a parent record*/  
     function uploadAttachment(filecontent, filename, filetype, parentId) {  
       var attachment     = new sforce.SObject('Attachment');  
       attachment.Name    = filename;  
       attachment.IsPrivate  = false;  
       attachment.ContentType = filetype;  
       attachment.Body    = filecontent;  
       attachment.Description = filename;  
       attachment.ParentId  = parentId;  
       var result = sforce.connection.create([attachment]);  
       if(!result[0].getBoolean("success")){  
         alert('PDF download failed.');  
       }  
       else if({!$CurrentPage.parameters.retUrl != NULL}){  
         window.location.href = '/{!$CurrentPage.parameters.retUrl}';  
       }  
     }  
     /*Method to convert the attachment to base64 and attach the same to a record*/  
     function reqListener () {  
       var reader = new window.FileReader();  
        reader.readAsDataURL(this.response);   
        reader.onloadend = function() {  
               base64data = reader.result.replace('data:application/pdf;base64,','');   
               if(base64data.indexOf('text/html') == -1){        
                 uploadAttachment(base64data,'{!$CurrentPage.parameters.pdfName}','application/pdf','{!$CurrentPage.parameters.pid}');  
               }  
               else{  
                 alert('Please check the PDF source URL.');  
               }  
        }  
     }  
     /*Method to request the PDF and retreive the BLOB*/  
     function loadPDF(){  
       sforce.connection.sessionId = '{!$Api.Session_ID}';  
       var oReq = new XMLHttpRequest();  
       oReq.onload = reqListener;  
       oReq.responseType='blob'  
       oReq.open("get", "{!$CurrentPage.parameters.pdfSrcUrl}", true);  
       oReq.send();  
     }  
   </script>  
   <apex:outputPanel rendered="{!($CurrentPage.parameters.pdfSrcUrl != NULL && $CurrentPage.parameters.pid != NULL && $CurrentPage.parameters.pdfName != NULL)}">  
     <script>  
       loadPDF();  
     </script>  
   </apex:outputPanel>  
 </apex:page>