Often its required to embed a Visualforce page into either the Home tab or the left hand navigation.  This is not natively possible, however a common work around is to use a homepage component with an iFrame referencing your Visualforce page.

The next challenge is that the iFrame height is static as defined in the component.  Javascript hack to the rescue!

<apex:page id="hompage" title="Homepage Visualforce"
    controller="" showheader="false" sidebar="false" cache="false" standardStylesheets="false">
<script type="text/javascript">
    function resizeFrame() {
	var parentIFrame = parent.document.getElementById('homepageComponentiFrameId');
            if ( parentIFrame != null ) {
                 if ( document.body.clientHeight != null && document.body.clientHeight > 0 )
                      parentIFrame.height = document.body.clientHeight;
                  else {
                       parentIFrame.height = document.height+"px";
                  }
              }
         }
</script>
     <body onload="resizeFrame();">
your page contents here...
</body>
</apex:page>

This simple example Visualforce page calls a simple Javascript method onload.  The resizeiFrame method could also be called by a chunk of Javascript loaded at the end of the page to save adding the body tags, which may break the markup.

You then need your homepage component which looks like this:

<iframe id="homepageComponentiFrameId" src="/apex/yourVisualforcePage"
width="100%" height="5px" frameborder="0" scrolling="no"> </iframe>

The important part here is to make the id of the iFrame the same as the value in the parent.document.getElementById line in the Javascript in the Visualforce page.

You will find the page load in the frame and then resize, the resize is sometimes noticable if your browser is slower at rendering the page and executing Javascript.

Let me know if this works for you, I have used this many times in an emea org.

Over the next two weeks I will be preparing to get Add Followers for Chatter available for Free from the AppExchange.  The product itself is ready, feel free to install in a Sandbox and test, all that is outstanding is making it ready for the AppExchange.  The steps involved in this are a mystery to me right now – in between reading up on this I will also make incremental improvements including support for multiple languages and any minor feedback I receive from BETA testers.

Add Followers for Chatter allows any user to make any other user subscribe to a records Chatter feed.  Users related to the record, any user lookup on the record itself, will be recommended.  You may also lookup any other user in the application to allow them to subscribe to the records Chatter feed.

Once available in the AppExchange I will be looking to extend the recommendations including user lookups from related records and also Case and Opportunity Teams both individually and as a team.

If you see an application for Add Followers for Chatter in your organisation and would like to help me with testing in your Sandbox use this link to install a Beta version.

BETA Install Link:
https://test.salesforce.com/packaging/installPackage.apexp?p0=04tA0000000FAWt

UPDATE Sunday 3rd February 2013: The solution discussed in this post is no longer viable .  This is due to Visualforce now being delivered from a different domain and therefore causing xdomain issues interacting with the iFrame.  Two new solutions are discussed in a new post, ‘Two methods for embedding a Report or Dashboard Chart in Visualforce or Standard Pagelayout’

More than once I have wanted to display a report within my application. Unfortunatly there is not a Visualforce component for displaying a report directly on a custom Visualforce page. I used to fall back on linking to the report as a compromise. It is just that, a compromise, so I decided to break a cardinal sin of modern web design and decided to use an iframe.

This is the report I want to display in my visualforce page. By simply load this URL into an iframe however, I will get all the fluff that surrounds the report. The Headers with global search, the report title, the tag bar, the filter options etc. To remove these I will need to employ javascript.

Report to show inline

The following code is for the Visualforce page to display a simple iframe. The smart bit is in the Javascript. The Javascript displays the report in the iframe whilst hiding the fluff around the report making the whole experience that little bit nicer.

<apex:page>
   <script type="text/javascript">
    function renderReportFrame(myIframe, reportId, hideHeader, hideTitle, hideProgressIndicator, hideFilters, hideTagbar) {
        if (myIframe != null) {
            myIframe = myIframe.childNodes[0];

            if (myIframe != null) {
                myIframe.onload = function() {
                bPageTitle

                    var phHeader = this.contentWindow.document.getElementById("phHeader");
                    var tabNavigation = this.contentWindow.document.getElementById("tabNavigation");
                    var section_header = this.contentWindow.document.getElementById("section_header");
                    var progressIndicator = null;
                    var progressIndicatorList = this.contentWindow.document.getElementsByClassName("progressIndicator");
                    if (progressIndicatorList!=null) progressIndicator = progressIndicatorList[0];

                    var bFilterReport = null;
                    var bFilterReportList = this.contentWindow.document.getElementsByClassName("bFilterReport");
                    if (bFilterReportList!=null) bFilterReport = bFilterReportList[0];

                    var bPageTitle = null;
                    var bPageTitleList = this.contentWindow.document.getElementsByClassName("bPageTitle");
                    if (bPageTitleList!=null) bPageTitle = bPageTitleList[0];

                    if (hideHeader!=null && hideHeader==true) {
                        phHeader.style.cssText = "display: none;";
                        tabNavigation.style.cssText = "display: none;";
                    }
                    if (hideTitle!=null && hideTitle==true) {
                        bPageTitle.style.cssText = "display: none;";
                    }
                    if (hideProgressIndicator!=null && hideProgressIndicator==true) {
                        progressIndicator.style.cssText = "display: none;";
                    }
                    if (hideFilters!=null && hideFilters==true) {
                        bFilterReport.style.cssText = "display: none;";
                    }
                    if (hideTagbar!=null && hideTagbar==true) {
                        section_header.style.cssText = "display: none;";
                    }

                };
            myIframe.src = "/" + reportId + "?pv1=var1";
            }
        }
    }
  </script>

  <apex:outputPanel id="theReportPanel" layout="block">
    <apex:iframe id="theReportFrame" scrolling="true" />
  </apex:outputPanel>

  <script type="text/javascript">
      renderReportFrame(document.getElementById("{!$Component.theReportPanel}"), "00OR0000000gr45", true, true, true, true, true, true);
  </script>
</apex:page>

Update 12/10/2011: Vanessen Munisamy has just provided a better solution. Salesforce already provides a hidden URL parameter to handle much of the JacaScript from this post.

A simple thing you could have done is to append the parameter ?isdtp=mn to the url in the iframe source, the report will appear without headers. ex. /00OG0000004AIHR?isdtp=mn

This is great and should mean the solution is future better future proofreader against report output facelifts. However, I believe use of ‘hidden’ URL parameters is generally considered unsupported so is not guaranteed.

Hot out of a discussion on twitter including @aognenoff @botoscloud @joshbirk @wesnolte and of course a bit of echoing from darylshaber there didnt seem to be a clear answer – but it did confirm the platforms gap for a logging solution.

Challenge

How can we get a scalable logging solution for force.com.  My company uses a custom solution that is documented in this post however this isnt particularly scalable, it is siloed from our on-premise logging solution, splunk, and requires a decent amount of data storage – and precious DML.

Current Solution

The current solution we use at its very simplest required one custom object and two classes.  On top of this solution we also have scheduled batch apex class to purge the logfiles daily.

Custom Object: LogFile__c
Fields: Body__c (Long Text Area(32000) ), Type__c (Picklist)

Logger Class

public without sharing class Logger {

private String logType;
private String toAppend;

public Logger(String logType) {
this.toAppend = ”;
this.logType = logType;
}

public void log(String message) {

System.Debug(message);
toAppend = toAppend + ‘\n’ + System.now().format() + ‘: ‘ + message;
}

public void commitLog() {
try {
System.debug(‘Committing Log: ‘ + toAppend);
LogFile__c log = createLog();
if(!Properties.IS_TEST_METHOD){
insert log;
}
toAppend=”;
} catch (Exception e) {
System.debug(‘Failed to commit logs: ‘ + e);
}
}

private LogFile__c createLog() {
LogFile__c log = new LogFile__c();
log.Type__c = logType;
if (toAppend.length() > 32000) {
toAppend = toAppend.substring(0, 31999);
}
log.Body__c = toAppend;
return log;
}
}

public with sharing class LoggerFactory {
private static Map<String,Logger> loggers = new Map<String,Logger>();

public static Logger getLogger(String name) {
if (loggers.containsKey(name)) {
return loggers.get(name);
} else {
System.debug(‘…\n…\n…\nCreating new logger for ‘ + name);
Logger logger = new Logger(name);
loggers.put(name, logger);
return logger;
}
}
}

Example Trigger

trigger ContactBeforeInsert on Contact (before insert) {

Logger logger = LoggerFactory.getLogger(‘Contacts’);
logger.log(‘ContactBeforeInsert: ‘);
try {
logger.log(‘Calling [methodname], Number of SOQL: ‘ + Limits.getQueries());
//call method
} catch(Exception e) {
//handle error (we actually have an errorlog custom obj too)
logger.log(‘exception details ‘ + e);

}
logger.log(‘Finished ContactBeforeInsert, Number of SOQL: ‘ + Limits.getQueries());

logger.commitLog();
}

A common requirement is for any easy was to get PDFs, spreadsheets, Word docs or even paper (yes, real world) paper into Salesforce.  All of the files are relatively easy.  You can upload files as Content to a Library – but how do you get a piece of paper into Salesforce as Content?

  1. Scan document
  2. Save to computer
  3. Login to Salesforce
  4. Navigate to Content
  5. Upload file (this isn’t a great experience when using content, lots of nifty but slow AJAX)

So the point I am trying to make is this doesnt work great – imagine doing that for all documents sent to and received from your customer, all contracts etc. etc.

What I have is a way for you to skip steps 2,3,4 & 5.  Rather than scanning the document and creating a local file many scanners support directly emailing that attachment. So why not email it directly into Salesforce, and why not strip out meta data from the emails subject or body and add that information to the content record? Well this is Email2Content.  So, now we are getting that paper into Salesforce without even logging in – the same idea works for files.  All your doing is emailing a file to an email address.  So all those files you need to upload one by one can be attached to one email* and sent – easy peasy. *max file size of about 5MB, enough for most PDFs and general documents.

The really cool bit here is being able to populate fields using for example the subject.  In the example here we are using Email2Content to upload Id Documents into Salesforce and using the subject to tag the content.

Email2Content Mappings Tab

From the Email2Content Mapping tab you can configure all of your mappings…

Email2Content Mapping Tab

Email2Content Mapping record for ID Docs

To create an Email2Content Mapping you will first need to create your email service (this is in the develop menu of Setup but is very easy, no code at all).  Once you have your email service Salesforce will generate a long email address for you, this is what you see in the ‘Email2Content Email Service Address Field’.  Then all you need is a descriptive name and to provide a Library ID if you want to automatically associate the content with a Library.

Once you have created your mapping record your ready to go.  In the example below we have also cr

eated a field mapping record.  In this example we are reading the subject of the email into the CSVTag field – this is the field which shows the tags.  You could easily set a custom field based on subject or just as a static value – or what I recommend is adding an ‘Uploaded by’ field and populating this based on [From Email].

So this is Email2Content in a nutshell.  I am currently reading up on ISVforce docs and aim to get it listed very shortly in the AppExchange. Any questions or feature requests please leave a comment.

Add Followers

March 29, 2011

UPDATE (Wednesday 30th March): To install Add Followers in your sandbox try this link, please provide feedback via comments.  This is a beta version will be submitting to AppExchange very soon subject to feedback.

https://test.salesforce.com/?startURL=%2Fpackaging%2FinstallPackage.apexp%3Fp0%3D04tA0000000Eh0S
Password: addfollowers

 

Here is a preview of an app in development. The idea is that you want to force someone to follow a record. A use case could be a project record that you want a consult to be aware of or a case that you want the technical specialist to follow.

The screenshot shows that a custom button launches a visualforce page that looks at all user lookups on the record and users that the current user follows to recommend a list of people you may want to force follow. Alternatively you cause a lookup to search for any user.

20110329-111905.jpg

Please add comments with any feedback. @dscach has already mentioned to ability to recommend related Acc/Opp/Case teams. Once v1 is in the AppExchange I will start looking at building in new features.

We began seeing the error ConcurrentPerOrgApex in our logs months ago. The error is caused when your org has 20+ concurrent apex requests running for longer than 20seconds.

When Spring ’10 was released we had reports from users that they were seeing this error in the UI. The users would have their requests interupted with a message telling them that Salesforce has stopped further requests being processed to protect ageing denial of service attacks. We also noticed a large increase in the number of these errors being logged.

Our integration receives XML messages from two databases. Each database sends these messages to two seperate queues, these queues are then processed by 4 threads each. (There are more queues/threads but it’s these ones where we experienced the errors).

At the end of the day a batch job runs in one of those external databases that causes 25k+ records to be updated, one by one – causing the XML messages to be sent to our Salesforce integration queues for each record updated. Our integration then tries to upsert these records in Salesforce. It was this process that was causing the issue.

The problem was that when we hit this error Salesforce returned a generic error message UNKNOWN_EXCEPTION. The explanation of the cause is only available in the exception message. Our middleware handles all transient errors by pausing for a designated time and retrying up to three times, because this was not a transient error message our middleware just scrapped the offending message and went to the next one further locking our org.

UPDATE: This was drafted a long time ago and never published. It turned out the ConcurrentPerOrgApex error message was introduced in Winter ’09 as an API only error. So it would cause integration errors but would not cause issues for users. In Sprint ’10 the effects of this error were extended to users. So if your API is consuming all concurrent apex requests then users will be blocked receiving an error message when they try to execute anything causing apex to fire. The error hints at potential Denial of Service (DOS) attacks which hints at why the limit was introduced.

After understanding the error we concluded that we need to handle UNKNOWN_EXCEPTION as a transient error. This isn’t ideal however it resolves the problem as the pause gives salesforce time to wind down the existing apex requests.

This was not documented in release notes so just another reason to thoroughly regression test integration between releases.

Today I wanted to use the standard interface components pageBlock & pageBlockSection whilst adding a bit if colour to differentiate sections. As there is no style class attribute for pageBlockSection it’s not so straight forward.

Seems a shame to have to rebuild this functionality that comes for free with a pageBlockSection so I looked to JavaScript to solve this problem. The following function takes the pageBlockSection and the desired colour then adds that colour to HTML element as a background-color.

function colorPageBlock(pageblock, color) {
if (pageblock != null) pageblock.firstChild.style.cssText = "background-color: " + color + ";";
}

This is the result:

and here is the full code:

<apex:page standardController="Account">

<script>
function colorPageBlock(pageblock, color) {
if (pageblock != null) pageblock.firstChild.style.cssText = “background-color: ” + color + “;”;

}
</script>

<apex:form>

<apex:pageBlock title="My Content" mode="detail">
<apex:pageBlockSection id="redSection" title="My Content Section" columns="2">
<apex:inputField value="{!account.name}"/>
<apex:inputField value="{!account.site}"/>
<script>colorPageBlock(document.getElementById("{!$Component.redSection}"), "red");</script>
</apex:pageBlockSection>

<apex:pageBlockSection id="greenSection" title="My Content Section" columns="2">
<apex:inputField value="{!account.name}"/>
<apex:inputField value="{!account.site}"/>
<script>colorPageBlock(document.getElementById("{!$Component.greenSection}"), "green");</script>
</apex:pageBlockSection>

<apex:pageBlockSection id="orangeSection" title="My Content Section" columns="2">
<apex:inputField value="{!account.name}"/>
<apex:inputField value="{!account.site}"/>
<script>colorPageBlock(document.getElementById("{!$Component.orangeSection}"), "orange");</script>
</apex:pageBlockSection>
</apex:pageBlock>

</apex:form>

</apex:page>

 

Chatter has been out for a while but there isn’t any Visualforce support yet. Until now if you want to put Chatter on a Visualforce page it has to be DIY. Chatter Visualforce tags should be included in Winter ’11 but I am not aware of what’s included or how comprehensive or customisable they will be.

This is how you add a users profile pic to a visualforce page.

<apex:outputlink value=”/{!uid}”>

<apex:image value=”/userphoto?id={!uid}&v=1&s=F” />

</apex:outputlink>

What you care about here is the link:
“/userphoto?id={!uid}&v=1&s=F”
it’s made up of three parameters.

1. Id – User’s Id
2. v – Profile pic version number, not sure why you wouldn’t always use 1?
3. s – Size, either F (Full) or T (thumb)

So hope that helps, any other tips leave a comment.

I was asked this week why one if our custom objects didn’t have an owner. The answer didn’t come to me immediately so I thought I should share the answer in case I forget again.

If you find an object with no owner field then this means it is the ‘detail’ part of a Mater-Detail relationship. When an object is the ‘detail’ then it inherits the sharing model and the owner from it’s ‘master’. So if you get asked like I did “how do we assign this object to a user” then you are probably using the wrong relationship and should use a custom lookup field to the ‘detail’. Where that is not possible a custom lookup to the user object usually does the trick. Drawbacks of this are you cannot report or create views on ‘My Widgets’. Another reason to think Reich before creating any relationship.

In short, no owner field, then your looking at a child object and the owner is inherited.

<span>%d</span> bloggers like this: