RSS feed for blog Linkin Skype Mail Me Twitter

Stickfight

Blog Category: salesforce

SalesForce for Domino Dogs 3: Web Query Save Agents

“WebQuerySave” / “PostOpen” and all its siblings have been a bastion of Domino and Notes developments since time out of mind and indeed they exist in a near identical form in Salesforce but just called Triggers

Just like Notes/Domino has different events that let code ‘Do Stuff’ to records e.g. “WebQueryOpen”,”OnLoad”, “WebQuerySave” etc etc, Salesforce has the same sort of thing, in their case they are broken down into 2 parts: Timings and Events

Timings: Before and After

Before: The Event has been started but the record has not been saved, this maps basically to the “Query” events in Domino.

If you want to calculate fields and stuff and change values in the record you are saving, this is the time to do that, you don’t have to tell it to save or commit the records as you normally would, it will run a save after your code is run.

After: The record has been saved, all field values have been calculated, then the After event is run.

If you want to update other objects on the basis of this record being created/saved do it here, you can’t edit the record you are saving, but lots of useful bits such as the record id and who saved it are available in the After event 1

Events: Insert, Update, Delete and Undelete

These are exactly what they say there, Insert is like a new document creation, Update is editing an existing document, etc etc

This then gives us a total set of different event types of:

  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete2

Now you can have a separate trigger for each of these events, but I have found that this bites you in the bum when they start to argue with each other and hard to keep straight when things get complex, so I just tend to have one trigger for all events and a bit of logic in it to determine what’s going to happen when

Here is my Basic template I start with on all my triggers

trigger XXXXTriggerAllEvents on XXXX (
    before insert,
    before update,
    before delete,
    after insert,
    after update,
    after delete,
    after undelete) {
            if(Trigger.isInsert || Trigger.isUpdate) {
                if (Trigger.isUpdate && Trigger.isAfter) {
                   MYScriptLibarary.DoStuffAfterAnUpdate(Trigger.New, Trigger.OldMap);
                } else if (Trigger.isInsert) {
                    //Do some stuff here to do with when a new document being create, like sending emals
                }
            }
}


As you can see you can determine what event you are dealing with by testing for “.isInsert” or “.isAfter” and then run the right bit of code for what you want, again I like to keep everything in easy sight, so use functions when ever I can with nice easy to understand names.

In the above case, I want to check a field after there has been an update to see if it has been changed from empty to containing a value. you can do this with the very very useful ‘Trigger.New’ and ‘Trigger.OldMap’) as you can see below

public with sharing class MYScriptLibarary {

    public static void DoStuffAfterAnUpdate(List<XXXX> newXXXX, Map<ID, XXXX> oldXXXX) {

                for (XXXX curentXXXX : newXXXX) {
                    if(!String.isBlank(curentXXXX.MyField) && String.isBlank(oldXXXX.get(curentXXXX.Id).MyField) ) {
                        system.debug('OMG!!! MYField changed DO SOMTHING');
                    }
                }

          }

}


So we are taking the list of objects3 that have caused the trigger to run ie “Trigger.New”, looping through them and comparing them to the values in the Trigger.OldMap (which contain the old values) to see if things have changed.


So that is the theory over, you can see existing triggers by entering Setup and searching for “apex triggers”



BUT you cant make them from there, you make them from the Object you want them to act on.

Lets take the Case object for an example



In setup you search for case, and click on “Case Triggers” and then on “New”



That will give you the default trigger…. lets swap that out for the all events trigger I showed above



Better, then just click save and your trigger will be live. simples..

Now there is an alternative way to make triggers, and you do sometime have to use it when you want to create a trigger for an object that does not live in the setup, such as the attachment object.



You will first need to open the Developer Console up (Select your Name in the top right and select “Developer Console”), then select File —> New —> Apex Trigger



Select “attachment” as the sObject and give it a sensible name.



And now you can do a trigger against an object that normally you don’t see.

Final Notes:
  1. Salesforce Process flows can fight with your triggers, if you get “A flow trigger failed to Execute” all of a sudden, go look to see if your power users have been playing with the process flows.
  2. Make sure you have security set correctly, particularly with community users, both security profiles and sharing settings can screw with your triggers if you cant write or see fields.
  3. As always make sure you code works if there are millions of records in Salesforce. CODE TO CATER TO LIMITS.

  1. You know that pain in the butt thing you sometimes have to do with Domino when you have to use the NoteID rather than that Document ID before a document is saved this gets round that issue. 

  2. Yes eagle eyes, there is no “before undelete”. 

  3. You are best to handle to handle all code in terms of batches rather than the single document you are used to in Domino, we will handle batching in a later blog, but just take my word for it at the moment 

SalesForce for Domino Dogs 2: Scheduled Agents

Welcome to the second part of the Salesforce for Domino Dogs series. This one is a monster, but don’t worry we will be revisiting and clearing up some of the complex parts in other blog posts. What was a simple thing in Domino is quite complex in Salesforce and for a variety of very good reasons. So… scheduled agents.


Scheduled Agents: These little sods are the pain of many a Domino admin’s life. Personally I blame them for the lock-down of many a Domino server from the free-for-all that was so empowering to users, but sometimes there is no other way to get round limits or deal with certain triggered activities.

In Salesforce scheduled processes are a bit more complex than you might be used to, and this is not just a Salesforce thing, but a cloud thing—no cloud provider wants their platform just churning along in the background eating up cycles and I/O time.

So let’s break it down:

  1. The code that does stuff
  2. The scheduled task that the code sits in
  3. The schedule itself

1) The Code

So this CAN just be any bit of Apex you want, but most of the time you will actually end up using batch apex. Batch Apex is a whole set of articles in its own right, but in this case it’s just a way of getting round the Apex limits.

… hmmm that does not help. OK let me explain:

You know how with Domino scheduled agents, they will only run for so long before the agent manager shuts it down? This is to stop you writing rubbish code that screws up the system. Apex has a load of limits just like that, and the one that hits quite often is the limit that you can only send 10 emails using Send() in a given transaction (you can send 1000 bulk email per day). To get round this limit you have to “batch”, or break up your code into chucks. In Domino this would be like saying we want to process a whole view’s-worth of documents, but in chunks of say five documents at a time.

An empty bit of batch apex looks like this:

global class NotifiyAllUsersInAView implements Database.Batchable<sObject> {

    // The start method is called at the beginning of the code and works out which objects this code is goign to run agains.
    // It uses an SOQL query to work this out
    global Database.QueryLocator start(Database.BatchableContext BC){

    }

    // The executeBatch method is called for each chunk of objects returned from the start function.
    global void execute(Database.BatchableContext BC, List<Contact> scope){

    }

    //The finish method is called at the end of a sharing recalculation.
    global void finish(Database.BatchableContext BC){

    }

}

Let’s take it apart. First we will use the “start” function to get the list of objects we want to work through, so we take the empty function:

    // The start method is called at the beginning of the code and works out which objects this code is goign to run agains.
    // It uses an SOQL query to work this out
    global Database.QueryLocator start(Database.BatchableContext BC){

    }

… and add a search to say get all “contacts” in Salesforce. We only need the email address for these contacts1 so we add that as one of the fields which it gives us:

    // The start method is called at the beginning of the code and works out which objects this code is going to run against
    // It uses an SOQL query to work this out
    global Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator([SELECT Id, Email FROM Contact]);
    }

Next we want the empty “execute” function which will do whatever we want with each chunk of objects it is sent:

    // The executeBatch method is called for each chunk of objects returned from the start function
    global void execute(Database.BatchableContext BC, List<Contact> scope){

    }

So in this horrible bit of code, the chunk of objects is passed in a reference called “scope” — we are then just iterating the objects and sending an email for each contact (you can see the email address stipulated in the “start” being passed in using “c.Email”):

    // executeBatch method is called for each chunk of objects returned from the start function
    global void execute(Database.BatchableContext BC, List<Contact> scope){
      for(Contact c : scope){
          Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
          String[] toAddresses = new String[] {c.Email};
          mail.setToAddresses(toAddresses);
          mail.setSubject('Another Annoying Email');
          mail.setPlainTextBody('Dear XXX, this is another pointless email you will hate me for');
          Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
       }
    }

Finally we need an empty “finish” function which runs when all the batches are done:

    //The finish method is called at the end of a sharing recalculation.
    global void finish(Database.BatchableContext BC){

    }

So let’s send a final email notification to the admins:

    //The finish method is called at the end of a sharing recalculation
    global void finish(Database.BatchableContext BC){
        // Send an email to admin to say the agent is done.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {emailAddress};
        mail.setToAddresses(toAddresses);
        mail.setSubject('Agent XXX is Done.');
        mail.setPlainTextBody('Agent XXX is Done.');
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }

Put it all together and you get:

global class NotifiyAllUsersInAView implements Database.Batchable<sObject> {

    // String to hold email address that emails will be sent to.
    // Replace its value with a valid email address.
    static String emailAddress = 'admin@admin.com';

    // The start method is called at the beginning of the code and works out which objects this code is goign to run agains.
    // It uses an SOQL query to work this out
    global Database.QueryLocator start(Database.BatchableContext BC){
        return Database.getQueryLocator([SELECT Id, Email FROM Contact]);
    }

    // The executeBatch method is called for each chunk of objects returned from the start function.
    global void execute(Database.BatchableContext BC, List<Contact> scope){
      for(Contact c : scope){
          Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
          String[] toAddresses = new String[] {c.Email};
          mail.setToAddresses(toAddresses);
          mail.setSubject('Another Annoying Email');
          mail.setPlainTextBody('Dear XXX, this is another pointless email you will hate me for');
          Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
       }
    }

    //The finish method is called at the end of a sharing recalculation.
    global void finish(Database.BatchableContext BC){
        // Send an email to admin to say the agent is done.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
        String[] toAddresses = new String[] {emailAddress};
        mail.setToAddresses(toAddresses);
        mail.setSubject('Agent XXX is Done.');
        mail.setPlainTextBody('Agent XXX is Done.');
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
    }

}

So now we need to call this code

2) The Scheduled “Agent”

The code we have just written won’t run in a schedule on its own, we need to wrap it up in a bit of code that can run on a schedule and decide how big the chunks will be. In this case they can’t be more than 10 as we will hit the Apex limits for sending emails. An empty schedule wrapper looks like this (I have called mine ‘Scheduled_Agent’ but you can call it anything):

global class Scheduled_Agent implements Schedulable{
    global void execute (SchedulableContext SC){

    }
}

Now let’s create a new instance of the batchable code we created in section 1, tell it we want it to run in batches of 5 records or objects, and tell it to execute.

global class Scheduled_Agent implements Schedulable{
    global void execute (SchedulableContext SC){
      Integer batchSize = 5;

      NotifiyAllUsersInAView batch = new  NotifiyAllUsersInAView();
      database.executebatch(batch , batchSize);
    }
}

Code bit all done!

3) The Schedule

Now it comes time to actually schedule the code to run at a certain time, you can set this up via the user interface by going into Setup, searching for “Apex Classes”, and selecting the result:


Select “Scheduled Apex”


As you can see, the options are limited to, at most, a daily run—you can’t specify it to be any more frequent. However, we need to run to more often than that2.

First open up your developer console, by selecting your name on the top right and picking it from the drop-down.


Now open up the “Execute Anonymous Window” from the debug menu.


You can now run Apex code manually, and as such you can schedule jobs with a load more precision using a Cron String. In this case we want to run the agent every 10 mins within the hour, so we create a new instance of our “Scheduled_Agent” scheduled class and schedule it appropriately:


Click “Execute” and you can see the jobs have been scheduled. It should be noted that you can only have 100 of these in your org and this uses up 6 of them, so some planning would be good.


And there you go, scheduled agents. Let the legacy of horror continue!


  1. When you get an object via SOQL, you ask for all the fields you want, this is not like getting a Notes Document you don’t just get access to all the document fields automatically. 

  2. Well we don’t but you just know someone will demand it to be sent more often. 

SalesForce for Domino Dogs 1: Profile Documents

Following on from the Initial Session “Salesforce for Domino Dogs” that Paul Mooney and I did at Engage and a modified version of which that has just been presented at #DNUG I figured that a series of dev articles on how you would do a job in Salesforce that you had always taken from granted in Domino might be a good idea, because:

  1. It would give an honest way of comparing features between the 2 systems shorn of the hype/marketing/platform bashing, that frankly gets on my thungers from both sides.
  2. It will hopefully help people trying to integrate the 2 systems.
  3. As IBM are one of the largest Salesforce consultancies in the world, it is something a champion should do.
  4. The Salesforce community is very short on this kind of thing given its size in comparison to traditional IBM communities and with people like René in it I want to try and help improve it.

These articles are not in any order and are not meant to represent any form of training guide.

So lets get started, first up: Profile Documents!!


In Domino you tend to store config settings for an app in a profile document1 for all your one off settings

To get the same features in Salesforce you use a ‘custom setting’ which does exactly the same job and has one huge advantage over using a normal Salesforce custom object that could do the same job.

(It should be noted that Domino profiles are nothing like Salesforce profiles)

To create a custom setting, go into Setup and search for “Custom Settings”



Click on the “New” button, and fill in some sane details, for normal configs i.e. stuff you would use system wide, select a setting type of “List”, if you want to use them for things like default values in fields and formulas then select “Hierarchy”



Click ‘Save’ and you now have a custom setting object, you can add one or more fields to it just as you would any other object in Salesforce



Added all the fields you want?, lets put in some data. if you go back to the list of custom settings you will see that you now have a “manage” link, click this



Then click “New”



Fill in the fields just like you would on a normal form, if this is a Setting there is only going to be one of, I tend to give it the same title as the name of the object to keep things simple, in this case “General Settings”, if you are going to use it multiple times than give it a name that will make sense in the context of your code



All done. now we can use the setting in our code and see the reason why we would use them vs. a normal custom object.

As you can see from the code below, you don’t have to use a Select statement, which means getting the settings wont count against your apex limits, HORRAY!!!

You just have to create a new instance of the setting object then you can just “getInstance” with the Name of the object you created to get the document back.

General_Settings__c generalSettings = General_Settings__c.getInstance('General Settings');
String thirdPartAppURL = '';
if (null != generalSettings) {
     thirdPartAppURL = generalSettings.Third_Part_App_URL__c;
    
} 


Simples..


  1. Well you are supposed to, but you never do thanks to the evil caching and the fact the are are a sod to just copy around, so you end up with a config document and a view you store them in. 

Editable salesforce templates

Editing an email that is generated via an Saleforce email template BEFORE it is sent is something that I have had a few clients grumble over, the feature is baked into the Quote object which makes sense, but you try telling clients they cant have it for Order objects….

So this is a basic solution that gets round this problem and is the basic set-up for expanding it to solve all the issues, in this case I am solving the most common issue I have come access, in that you are generating a order recept or delivery note pdf using the ‘renderAs=”PDF”’ option in an email template and want to send the email with a custom message to one or more people that you decide at the time of sending.

To do that we are going to create a new visual force page that resembles a normal email and use it to fill in the details before we generate the email template


SFEditableEmail03.png

1) Create a new email custom object

This is just a basic object with the fields we need for the email, and a lookup field to the parent Order


New Custom Email Object

Note: For brevity I’m leaving out the creation of the layout for the custom object and the adding of the related list to the Order Layout

2) Add some extra Fields to your Order object

These are the temporary fields that we are using to actually send the template email, resist the urge to set the To/CC/BCC as email fields as that will stop you having multiple recipients.


Extra Fields

Obviously these fields have to be editable to all but not part of the order layouts so they don’t show up.

3) Create a new “Template Editor” Visualforce Page

This page is as simple or complex as you can want it, my one here takes email address separated by a comma but yours can be a really posh contact lookup


<apex:page controller="sendOrderPDFEmail">
    <apex:messages />
    <apex:pageBlock title="Email for Order: {!order.Name}">
    <p>Fill out the fields below and click "Send email"</p>
    <apex:form ><br/><br/>

        <apex:outputLabel value="Template To Attach" for="chooseTemplate"/>: <br/>
        <apex:selectList id="chooseTemplate" value="{!template}" size="1">
            <apex:selectOption itemValue="Template1" itemLabel="Template1"/>
            <apex:selectOption itemValue="Template2" itemLabel="Template2"/>
            <apex:selectOption itemValue="Template3" itemLabel="Template3"/>
        </apex:selectList>
        <br/><br/>  
    
        <apex:outputLabel value="To" for="To"/>: <br/>
        <apex:inputText value="{!to}" id="To" maxlength="255" style="width: 300px;"/>
        <br/><br/>  
        
        <apex:outputLabel value="CC" for="CC"/>: <br/>
        <apex:inputText value="{!cc}" id="CC" maxlength="255" style="width: 300px;"/>
        <br/><br/> 
        
        <apex:outputLabel value="BCC" for="BCC"/>: <br/>
        <apex:inputText value="{!bcc}" id="BCC" maxlength="255" style="width: 300px;"/>
        <br/><br/> 
    
    
        <apex:outputLabel value="Subject" for="Subject"/>: <br/>
        <apex:inputText value="{!subject}" id="Subject" maxlength="255" style="width: 500px;"/>
        <br/><br/>
        
        <apex:outputLabel value="Body" for="Body"/>: <br/>        
        <apex:inputTextarea value="{!body}" id="Body" richText="true" rows="20"/>
        <br/><br/>
        
        <apex:commandButton value="Send Email" action="{!send}"/>
    </apex:form>
    </apex:pageBlock>
</apex:page>


4) Create the “Send” code (don’t forget to create your test code).

I have just put comments on the code, as its not complex and follows the flow chart at the top of the blog.


public class sendOrderPDFEmail {


    public String template { get; set; }
    public String cc { get; set; }
    public String bcc { get; set; }
    public String to { get; set; }
    public String subject { get; set; }
    public String body { get; set; }
    
    
    public Account contactLookup { get; set; }
    private final Order order;

    public sendOrderPDFEmail() {
        //Get the Id of the order that we are working on
        template = '';
        String OrderId = ApexPages.currentPage().getParameters().get('id');
           
        order = [SELECT Name, ID
        FROM Order
        WHERE Id = :OrderId];
        
    }

    public Order getOrder() {
        return order;
    }


    public PageReference send() {
    
    if( !String.isBlank(cc)) {
        cc = cc.trim();
    }
    
    if( !String.isBlank(bcc)) {
        bcc = bcc.trim();
    }
    
    if( !String.isBlank(to)) {
        to = to.trim();
    }
    
    
    //Mail email Order object
    orderemail__c con = new orderemail__c(
        Address_CC__c=cc,
        Address_BCC__c=bcc,
        Address_to__c=to,
        Body__c=body,
        parent_order__c=order.id,
        Subject__c=subject,
        Template__c=template
        );
    insert con;

    //Store the temp values in the Order
    order.le_Address_BCC__c=bcc;
    order.le_Address_CC__c=cc;
    order.le_Address_to__c=to;
    order.le_Body__c=body;
    order.le_Subject__c=subject;
    order.le_Template__c=template;
    
    
    update order;

    if ( String.isBlank(to) ) {
         to = 'test@test.com';
     }
    
     // Construct the list of emails we want to send
     List<Messaging.SingleEmailMessage> lstMsgs = new List<Messaging.SingleEmailMessage>();
    
     Messaging.SingleEmailMessage msg = new Messaging.SingleEmailMessage();
     
     
     //set the email tempalte from the name chossen on the email form
     EmailTemplate[] emailTemplate = [select id from EmailTemplate where Name=:template];
     if (emailTemplate.size() > 0) {
          msg.setTemplateId( emailTemplate[0].id );
          
     } else {
         msg.setPlainTextBody('No Template Provided');
     }

     // Trim and convert the comma delimted string to a suitable recipiant list for the email.
     String[] trimmedtoarray;
     if (!String.isBlank(to)) {
        String[] toarray = to.split(',');
        trimmedtoarray = new String[toarray.size()];
        Integer k = 0;
        for (String singlEmail: toarray) {
           trimmedtoarray[k++] = singlEmail.trim();
        }
        msg.setToAddresses(trimmedtoarray);
     }
     
     if (!String.isBlank(cc)) {
         String[] ccarray = cc.split(',');
         String[] trimmedccarray = new String[ccarray.size()];
         Integer i = 0;
         for (String singlEmail: ccarray) {
             trimmedccarray[i++] = singlEmail.trim();
         }
         msg.setCcAddresses(trimmedccarray);
     }
     
    
     if (!String.isBlank(bcc)) {
         String[] bccarray = bcc.split(',');
         String[] trimmedbccarray = new String[bccarray.size()];
         Integer j = 0;
         for (String singlEmail: bccarray) {
            trimmedbccarray[j++] = singlEmail.trim();
         }
         msg.setBccAddresses(trimmedbccarray);
     }
     
     
     // Templates do need an object (Contact that he pdf will be used to generate against) for things like first name etc
     // So we are going to pick the first contact on the to list.
     Contact c = [select id, Email from Contact where email = :trimmedtoarray[0] limit 1];
     
     msg.setWhatId( order.id );
     msg.setTargetObjectId(c.id);
     
    
    
    
     lstMsgs.add(msg);

     //Send the email
     Messaging.sendEmail(lstMsgs);


    // clear out the temp fields we used to generate the email.
    order.le_Address_CC__c='';
    order.le_Address_BCC__c='';
    order.le_Address_to__c='';
    order.le_Body__c='';
    order.le_Subject__c='';
    order.le_Template__c='';
    
    
    update order;
        
        //Send the user back to the Order
        PageReference backToQuotePage = new PageReference('/' + order.id);
        backToQuotePage.setRedirect(true);
        return backToQuotePage;
    }
    
    
}


5) Create a button in the Order object to launch your “Email” screen


End Email Button

6) Update your Email template

Now update your email templates to use the “le_Body__c” and “le_Subject__c” fields for their email contents,

<messaging:emailTemplate recipientType="Contact"
    relatedToType="Order"
    subject="{!RelatedTo.le_Subject__c}"
    replyTo="orders@XXXX.com">
    <messaging:htmlEmailBody >

        <html>
        <c:EmailStyle />
        <body>
            <apex:outputText value="{!RelatedTo.le_Body__c}" escape="false"/>
        </body>
        </html>
    </messaging:htmlEmailBody>

    <messaging:plainTextEmailBody >
    <apex:outputText value="{!RelatedTo.le_Body__c}" escape="false"/>
    </messaging:plainTextEmailBody>

<messaging:attachment renderAs="PDF" filename="GeneratedEmail.pdf">
      <style>
      @page {
      size: A4 landscape;
      margin-top: 1.0cm;
      margin-left: 1.0cm;
      margin-right: 1.0cm;
      margin-bottom: 7cm;
        @bottom-center {
          content: element(footer);
        }

      }
      </style>

        <html>
        <c:EmailStyle />
        <body>
        
        //POSH PDF STUFF HERE

        </body>
        </html>
</messaging:attachment>

</messaging:emailTemplate>


Note:

So PDF’s generated by Email templates in Salesforce are not saved and you cant get hold of them, so we are not saving them in the custom email object, if you do need that, then you will need to convert your email templates to visual force pages then render as PDFs and save them in the custom objects.

Salesforce read mode hide-when hack

It seems the need for hacks has not gone away with the move to cloud, but you do have to be more careful, as I have already found out Salesforce can break your custom code at will.

So that being true one of the things that SalesForce does not seem to have that every other framework does is a simple an powerful “hide when”, IMHO this feature should be present in every line button and object on every page and most platforms do,

It is the most requested feature for customisation that I have come across, and this is the hack that I use for read mode documents or forms (which strangely is where the request is most often made) (and yes it is a hack)

We are going to just stuff some JavaScript into a apex page, you can see an example of it below

<apex:page standardController="Case" showHeader="false" sidebar="false">

<script>

myVar = checkforbutton();

function checkforbutton()
{
    if (typeof(parent.document.getElementsByName("searchArticles")[0]) != "undefined" )
    {
               if ( parent.document.title.indexOf("Customer Community") != -1) {        
                   parent.document.getElementsByName("searchArticles")[0].style.display = "none"; 
               }
    }
    else
    {
            window.setTimeout("checkforbutton();",100);
    }
} 


</script>

   
</apex:page>


Lets break it down

  1. We have to use “parent.document” to get JavaScript objects as when we use this page SalesForce will embed it inside a Iframe

  2. We have to do a wait loop that keeps looking for the object till it finds it (then stops), because SalesForce adds button objects in after the page loads so they wont be there when it first opens

    if (typeof(parent.document.getElementsByName("searchArticles")[0]) != "undefined" )
    {
            //do stuff
    }
    else
    {
            window.setTimeout("checkforbutton();",100);
    }


  1. When the button is confirmed to be there then we are going to check the page for a condition(s) and if it’s true/false we are going to hide the button/element on the basis of it, in the above example, if we are on the “Customer Community” version of a page, we don’t want to allow the users to search for articles, so we hide the “searchArticles” button, it’s not a security issue, we just don’t want to confuse the user experience by allowing this feature.

So we put this code into a Page

SF Page

This insert that page into the layout Object layout we want it to effect.

SF Page

that would normally give a big white space where the inserted form lives, so we make the page a 1% by 1px block


SFHIDEWHENHACK03.png

And that’s it, just save the layout, the button/”what ever” will hide when the page loads, a hack but a simple reliable hack

Now as always “hide when” is not security, remember to make sure that you don’t leave functions exposed and think this kind of thing covers you.

C3 charts on Saleforce Winter16

As @devinthecloud points out in his Blog post Salesforce Winter 16 wrecked C3 charts on the pages he and I had developed. he found the removal of <apex:form> fixed the issue on his pages, but on my page the charts were still broken, so instead of this:


Salesforce-C3-Charts-01.png

You get this


Salesforce-C3-Charts-02.png

I stripped the page down to a bare minimum that you can use to recreate the issue, see below,

<apex:page docType="html-5.0" 
           applyBodyTag="false"
           showHeader="true"
           readOnly="true"
           sidebar="false">
<html lang="en">
  <head>
      <!-- Static Resources for CSS -->
        <link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.css" rel="stylesheet"/>
    </head>
    

    <body>
      <div id="chart"></div>

      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js" />
      <script>
        var chart = c3.generate({
          data: {
            columns: [
  //            ["setosa", 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.2, 0.1, 0.1, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.2, 0.4, 0.2, 0.5, 0.2, 0.2, 0.4, 0.2, 0.2, 0.2, 0.2, 0.4, 0.1, 0.2, 0.2, 0.2, 0.2, 0.1, 0.2, 0.2, 0.3, 0.3, 0.2, 0.6, 0.4, 0.3, 0.2, 0.2, 0.2, 0.2],
              ["versicolor", 1.4, 1.5, 1.5, 1.3, 1.5, 1.3, 1.6, 1.0, 1.3, 1.4, 1.0, 1.5, 1.0, 1.4, 1.3, 1.4, 1.5, 1.0, 1.5, 1.1, 1.8, 1.3, 1.5, 1.2, 1.3, 1.4, 1.4, 1.7, 1.5, 1.0, 1.1, 1.0, 1.2, 1.6, 1.5, 1.6, 1.5, 1.3, 1.3, 1.3, 1.2, 1.4, 1.2, 1.0, 1.3, 1.2, 1.3, 1.3, 1.1, 1.3],
              ["virginica", 2.5, 1.9, 2.1, 1.8, 2.2, 2.1, 1.7, 1.8, 1.8, 2.5, 2.0, 1.9, 2.1, 2.0, 2.4, 2.3, 1.8, 2.2, 2.3, 1.5, 2.3, 2.0, 2.0, 1.8, 2.1, 1.8, 1.8, 1.8, 2.1, 1.6, 1.9, 2.0, 2.2, 1.5, 1.4, 2.3, 2.4, 1.8, 1.8, 2.1, 2.4, 2.3, 1.9, 2.3, 2.5, 2.3, 1.9, 2.0, 2.3, 1.8],
              ["setosa", 30],
  //            ["versicolor", 40],
  //            ["virginica", 50],
            ],
            type : 'pie',
            onmouseover: function (d, i) { console.log("onmouseover", d, i, this); },
            onmouseout: function (d, i) { console.log("onmouseout", d, i, this); },
            onclick: function (d, i) { console.log("onclick", d, i, this); },
          },
          axis: {
            x: {
              label: 'Sepal.Width'
            },
            y: {
              label: 'Petal.Width'
            }
          }
        });

        setTimeout(function () {
          chart.load({
            columns: [
              ["setosa", 130],
            ]
          });
        }, 1000);

        setTimeout(function () {
          chart.unload({
            ids: 'virginica'
          });
        }, 2000);

      </script>
    </body>
</html>
</apex:page>


This let me narrow it down to the Salesforce header.

when you set showHeader=”true” it breaks

if you set showHeader=”false” it starts working

As this is obviously and provably down to a change in winter 16 I can raise it with Salesforce and get a fix.

…….. Wrong!

I quote

“At times, Client Side JS conflict with SFDC header JS will collide with already defined functions with the same name.”

“the behaviour of JavaScript is inconsistent with Salesforce components.”

and

“as we don’t support JavaScript”

Ah it’s nice to see that young Internet companies have the same grasp of customer service as the traditional IT companies and thus it falls back on consultants to fix, in this case with an Iframe, I’m not a fan of Iframes normally but as Salesforce use them themselves there is precedent

And so we now split our single page into 2 pages, an inner content and an outer Iframe wrapper

First we take out the existing page (it was called “chart_test”), rename it to “chart_test_inner” and set showHeader=”false”

Then we put in a Iframe wrapper with the old name and a bit of CSS to make it size properly then point it to the inner content

<apex:page docType="html-5.0" 
           applyBodyTag="false"
           showHeader="true"
           readOnly="true"
           sidebar="false">
<html lang="en">
  <head>
  
  <style>
  .fluidMedia {
    position: relative;
    padding-bottom: 56.25%; /* proportion value to aspect ratio 16:9 (9 / 16 = 0.5625 or 56.25%) */
    padding-top: 30px;
    height: 0;
    overflow: hidden;
  }

    .fluidMedia iframe {
        position: absolute;
        top: 0; 
        left: 0;
        width: 100%;
        height: 100%;
    }
      </style>
  
  </head>
    
    <body>
    
    <div class="fluidMedia">
        <iframe src="../apex/chart_test_inner" frameborder="0" > </iframe>
    </div>
       
    </body>
</html>
</apex:page>


And now instead of a broken


We have a working


Hooray!!!

Latest Blogs