Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zokito/040cdc028879d271e721 to your computer and use it in GitHub Desktop.
Save zokito/040cdc028879d271e721 to your computer and use it in GitHub Desktop.

Custom Page Message Framework

This framework gives you a way to exert control over style when providing confirm/info/error/warn messages to your users in Visualforce pages, because let's face it: the default styles are not that pretty/easy to bring into line with a Client's style, particularly when being used in a Portal (I haven't personally worked on any clients using Communities yet, so can't comment on the aesthetics there!).

This framework is pretty simple and self-contained, including 100% test coverage for each class. The only things you need to bring to the party are Bootstrap css and js, and some very minor alterations to your controller and page files, which can be easily done with a quick find/replace or two.

Dependencies

Bootstrap css file : Optional, but strongly recommended unless you need to roll your own style
Bootstrap js file  : Optional, but required if you want your messages to be dismissable

Installation

Drop each of the following files into your org. No modifications are necessary

Controller_PageMessages.cls
Test_PageMessages.cls
pageMessages.cls
pageMessages.component

Usage

In your Apex code

Anywhere you would ordinarily do:

ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.SeverityLevel, 'Message'));

You now do the following (really easy to do a find/replace):

PageMessages.addMessage(new PageMessages.PageMessage(PageMessages.Severity.SeverityLevel, 'Message'));

In your Visualforce pages

Anywhere you would ordinarily do:

<apex:pageMessages/>

You now do the following (again, really easy to find/replace!):

<c:pageMessages/>

If you want users to be able to dismiss the messages, then do the following:

<c:pageMessages closableErrors="true"/>

If you're using Bootstrap, congratulations, everything looks wonderful.

Minor Gotchas

Messages added in a Controller Constructor

Messages added in a Controller Constructor won't "just work" as such (but there's an easy solution!). The easiest way to explain this (and the solution) is going to be with an example. Suppose your Controller Class creates a message inside the constructor, as follows:

public with sharing class Controller_TestMessages {
    
    public Controller_TestMessages() {
        PageMessages.addMessage(new PageMessages.PageMessage(PageMessages.Severity.CONFIRM, 'Beep Boop'));
    }
    
}

Now you would expect the message "Beep Boop" to appear on your page yes? No. It doesn't. The solution is easy. Just make a method like the following in your Controller class (name it what you like):

public void loadMessages() {
    //Shim to get PageMessages that were added in the Constructor into the page
}

And add this method as the action in the <apex:page> tag in your page, for example:

    <apex:page controller="Controller_TestMessages" 
        showHeader="false" sidebar="false" standardStylesheets="false" 
        action="{!loadMessages}">

Dismiss/Close buttons

If you don't use Bootstrap javascript (or js is disabled!), the dismiss/close buttons will not work. Note that the default position for this is that messages are not closable unless you explicitly add that to the pageMessages component tag on your page (see Usage section above).

Styling things yourself

If you don't use Bootstrap, it's gonna be really ugly as there's no inline style. The structure of the html produced by the pageMessages component is as follows, repeated for as many messages as you added in your apex:

<div class="alert alert-[info|warn|alert|error]">
    [<button class="close" data-dismiss="alert" type="button">&times;</button>]
    [Message]
</div>

Where:

[<button>...</button>]        : only appears when closableErrors is set to true;
[Message]                     : is (obviously?) the message you provided in your apex
alert-[info|warn|alert|error] : output depends on the severity level you indicated in your apex
public with sharing class Controller_PageMessages {
public String CustomPageMessages_ClosableErrorsRenderPlaceHolder {
get {
if(CustomPageMessages_ClosableErrorsRenderPlaceHolder == null) CustomPageMessages_ClosableErrorsRenderPlaceHolder = '';
return CustomPageMessages_ClosableErrorsRenderPlaceHolder;
}
private set;
}
public List<PageMessages.PageMessage> getPageMessages() {
return PageMessages.getMessages();
}
}
public with sharing class PageMessages {
public PageMessages() {
throw new UnsupportedOperationException('The PageMessages class cannot be instantiated. Maybe you were looking for PageMessages.PageMessage()?');
}
public enum Severity {CONFIRM,INFO,WARN,FATAL,ERROR}
private static List<PageMessage> StaticMessages {
get {
if(StaticMessages == null) StaticMessages = new List<PageMessage>();
return StaticMessages;
}
private set;
}
public class PageMessage {
private Map<Severity,String> SeverityStrMap = new Map<Severity,String>{
Severity.CONFIRM => 'confirm',
Severity.INFO => 'info',
Severity.WARN => 'warn',
Severity.FATAL => 'error',
Severity.ERROR => 'error'
};
public Severity SeverityLevel { get; set; }
public String SeverityStr { get; set; }
public String Message { get; set; }
public PageMessage (Severity theSeverity, String theMessage) {
this.SeverityLevel = theSeverity;
this.Message = theMessage;
this.SeverityStr = SeverityStrMap.get(theSeverity);
}
}
public static void addMessage(PageMessage thePageMessage) {
StaticMessages.add(thePageMessage);
}
public static List<PageMessage> getMessages(){
return StaticMessages;
}
}
<apex:component controller="Controller_PageMessages">
<apex:attribute name="closableErrors" description="Whether a clickable x will render on the error allowing you to close it. Defaults to false. Will not work wherever scripts are disallowed. Also will not work without bootstrap js" type="Boolean" required="false"/>
<apex:repeat value="{!PageMessages}" var="message" id="pageMessageRepeat">
<div class="alert {!IF(message.SeverityStr=='confirm','alert-success',IF(message.SeverityStr=='info','alert-info',IF(message.SeverityStr=='warn','alert-warning','alert-danger')))}"
<apex:variable var="CustomPageMessages_ClosableErrorsRenderPlaceHolder" value="{!CustomPageMessages_ClosableErrorsRenderPlaceHolder}" rendered="{!IF(closableErrors==TRUE,TRUE,FALSE)}" >
<button type="button" class="close" data-dismiss="alert">&times;</button>
</apex:variable>
{!message.Message}
</div>
</apex:repeat>
</apex:component>
@isTest
private class Test_PageMessages {
@isTest
static void testStandardUse() {
PageMessages.addMessage(new PageMessages.PageMessage(PageMessages.Severity.FATAL,'Test Message'));
System.assertEquals(1,PageMessages.getMessages().size());
}
@isTest
static void testFailDueToPageMessageInstantiation() {
PageMessages.addMessage(new PageMessages.PageMessage(PageMessages.Severity.FATAL,'Test Message'));
System.assertEquals(1,PageMessages.getMessages().size());
try {
PageMessages instantiated = new PageMessages();
System.assert(false);
}
catch (UnsupportedOperationException e) {
//Correct behaviour
}
}
@isTest
static void testController() {
PageMessages.addMessage(new PageMessages.PageMessage(PageMessages.Severity.FATAL,'Test Message'));
System.assertEquals(1,PageMessages.getMessages().size());
Controller_PageMessages controller = new Controller_PageMessages();
System.assertEquals('',controller.CustomPageMessages_ClosableErrorsRenderPlaceHolder);
System.assertEquals(1,controller.getPageMessages().size());
}
}
@Nahumancer
Copy link

Hey!, thanks for sharing this. This just saved me a lot of time :) and it works wonders. Just a comment, you have a typo on "pageMessages.component" line 5. The

opens but it is not closed.

Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment