How to convert a Visualforce apex:pageMessages to be lightning style
Here is what I do:
Class used by all my controllers:
VF Messages Abstract:
public abstract class VF_Messages_Abstract {
public void createError(ApexPages.Severity s, String message) {
ApexPages.addMessage(
New ApexPages.Message(
s, string.escapeSingleQuotes(message.escapeJava())
)
);
}
/**@description Indicates if a custom message is present*/
public boolean hasMessages {
get {
return ApexPages.hasMessages();
}
}
/**@description The custom error message */
public String errorMessage {
get {
if (hasMessages) {
return ApexPages.getMessages()[0].getDetail();
}
return null;
}
}
/**@description The type of message: 'error' or 'success' */
public string alertType {
get {
if (hasMessages) {
return ApexPages.getMessages()[0].getSeverity() == ApexPages.Severity.CONFIRM ? 'success' : 'error';
}
return 'error';
}
private set;
}
}
All controller for VF extend this class
Then in my vf pages I use the following:
Messages Toast
<!-- ERROR ALERT DIV -->
<apex:outputPanel layout="block" id="msg_block" style="display: none;">
<div id="err_wrapper"
class="slds-notify slds-notify--alert slds-theme--{!alertType} slds-theme--alert-texture"
role="alert">
<h2>
<div xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg aria-hidden="true"
class="slds-icon icon-text-email slds-icon--small slds-m-right--x-small">
<use xlink:href="{!URLFOR($Resource.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#' + if(alertType = 'success','check','ban'))}"></use>
</svg>
<span id="err_text"></span>
</div>
</h2>
</div>
</apex:outputPanel>
Helper Panel
<!--APEX VARIABLES DIV-->
<apex:outputPanel id="post_processing">
<script>
var hasMessages = {!hasMessages};
var errorMessage = "{!errorMessage}";
checkMessages();
</script>
</apex:outputPanel>
Check Messages Code (Using jQuery)
function checkMessages() {
if (hasMessages) {
$('#err_text').html(errorMessage);
$('[id$=msg_block]').show();
} else {
$('[id$=msg_block]').hide();
}
}
My buttons, action functions, etc rerender the post_processing
and msg_block
Creating an error In the controller when I catch or want to display an error I simply:
createError(ApexPages.Severity.[ERROR | CONFIRM],YOURMESSAGEHERE);
This allows me to control the messages, when to display it and which theme to show.
Note: The xmlns=
on the div
containing the SVG is a MUST as if you try to rerender without it the page will simply stall. Important trick to remember for anytime you are re rendering a container containing an SLDS SVG
If you need a example all put together let me know.
End result is (Error):
Or (Confirm)
I also have implemented a way to pass messages between transactions so if you need to say DML on button click and then Callout oncomplete of button click, if there was an error in the first step, when the second step happens it does not clear the message like it normally would (not shown in code)
Eric's answer inspired me to build a more generic tool that can support an arbitrary number of page messages of any severity.
Code
Controller
public class ToastBuilderController
{
public String getMessageJSON() { return JSON.serialize(getSafeMessages()); }
public List<PageMessage> getSafeMessages()
{
List<PageMessage> messages = new List<PageMessage>();
for (ApexPages.Message message : ApexPages.getMessages())
messages.add(new PageMessage(message));
return messages;
}
}
Serializable PageMessage
global class PageMessage
{
global final String summary, detail;
global final String sprite, theme;
global final ApexPages.Severity severity;
global PageMessage(Exception e)
{
this(ApexPages.Severity.FATAL, e.getMessage());
}
global PageMessage(ApexPages.Message message)
{
this(message.getSeverity(), message.getSummary(), message.getDetail());
}
global PageMessage(ApexPages.Severity severity, String summary, String detail)
{
this.severity = severity;
this.summary = summary;
this.detail = detail;
String s = String.valueOf(severity);
this.sprite = sprites.get(s);
this.theme = themes.get(s);
}
global PageMessage(ApexPages.Severity severity, String summary)
{
this(severity, summary, null);
}
static final Map<String, String> themes = new Map<String, String>
{
String.valueOf(ApexPages.Severity.FATAL) => 'error',
String.valueOf(ApexPages.Severity.ERROR) => 'error',
String.valueOf(ApexPages.Severity.WARNING) => 'warning',
String.valueOf(ApexPages.Severity.CONFIRM) => 'success'
};
static final Map<String, String> sprites = new Map<String, String>
{
String.valueOf(ApexPages.Severity.FATAL) => 'ban',
String.valueOf(ApexPages.Severity.ERROR) => 'warning',
String.valueOf(ApexPages.Severity.WARNING) => 'warning',
String.valueOf(ApexPages.Severity.CONFIRM) => 'success',
String.valueOf(ApexPages.Severity.INFO) => 'info'
};
}
Toast Builder Component
Probably should move the JS to a Static Resource
but I was trying to whip this together quickly.
<apex:component controller="ToastBuilderController">
<apex:slds />
<div class="slds-scope">
<div id="messages"></div>
</div>
<script>
(function (D, w) {
"use strict";
var T = w.ToastBuilder = w.ToastBuilder || {};
T.sprites = {
"success": "{!URLFOR($Resource.SLDS214, '/assets/icons/utility-sprite/svg/symbols.svg#success')}",
"error": "{!URLFOR($Resource.SLDS214, '/assets/icons/utility-sprite/svg/symbols.svg#error')}",
"info": "{!URLFOR($Resource.SLDS214, '/assets/icons/utility-sprite/svg/symbols.svg#info')}"
};
T.getMessages = function () {
return JSON.parse("{!JSENCODE(messageJSON)}");
};
T.clear = function () {
while (T.targetDiv.hasChildNodes()) {
T.targetDiv.removeChild(T.targetDiv.lastChild);
};
};
T.buildToasts = function (messages) {
if (!messages) messages = T.getMessages();
T.clear();
messages.forEach(function (message) {
T.buildToast(message);
});
};
T.buildToast = function (message) {
var toast = D.createElement("div");
toast.className = "slds-notify slds-notify--alert slds-theme--alert-texture";
if (message.theme) toast.className += " slds-theme--" + message.theme;
toast.setAttribute("role", "alert");
var innerDiv = toast.appendChild(D.createElement("h2"))
.appendChild(D.createElement("div"));
innerDiv.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
innerDiv.setAttribute("xmlns", "http://www.w3.org/2000/svg");
innerDiv.appendChild(D.createElement("span"))
.appendChild(D.createTextNode(message.summary));
var svg = innerDiv.appendChild(D.createElement("svg"));
svg.className = "slds-icon icon-text-email slds-icon--small slds-m-right--x-small";
svg.setAttribute("aria-hidden", true);
var use = svg.appendChild(D.createElement("use"));
use.setAttribute("xlink:href", T.sprites[message.sprite]);
T.targetDiv.appendChild(toast);
};
T.addError = function (message) {
T.clear();
T.buildToast({
theme: "error", sprite: "warning", summary: message
});
};
T.confirm = function (message) {
T.clear();
T.buildToast({
theme: "success", sprite: "success", summary: message
});
};
D.addEventListener("DOMContentLoaded", function () {
T.targetDiv = D.getElementById("messages");
T.buildToasts();
Object.freeze(T);
});
}(document, window));
</script>
</apex:component>
Usage
RemoteAction
(function (w) {
"use strict"
MyRemotingController.doSomething("Some Parameter", function (result, event) {
if (event.status) {
if (result.length === 0) {
// success handling
} else {
w.ToastBuilder.buildToasts(result);
// other error handling
}
} else {
w.ToastBuilder.addError(event.message);
}
});
}(window));
Remote Objects
(function (w) {
"use strict";
new SObjectModel.Contact().update(
["{!Contact.Id}"], {"SomeField__c": someValue}, function (error) {
if (error) {
w.ToastBuilder.addError(error);
// other error handling
} else {
// success handling
}
}
);
}(window));
ApexPages
If you use the standard approach to adding page messages to the page, calling buildToasts
will take the messageJSON
property and use it to build toasts corresponding to ApexPages.getMessages()
.
(Controller)
try
{
// do stuff
}
catch (SomeException e)
{
ApexPages.addMessages(e);
}
(Page)
<apex:commandButton action="{!doStuff}" oncomplete="ToastBuilder.buildToasts()" />
Below is the current approach I have found and used. But I am open to any other suggestions.
The idea and code originally is coming from: https://vishnuvaishnav.wordpress.com/2016/02/21/convert-standard-page-messages-in-lightning-design-system/
I have also written a blog about this idea at: http://sfdcinpractice.com/index.php/2016/11/22/convert-apexpagemessages-slds-style/
Basically, it is a little bit hacking into the Visualforce page by changing the style class into slds ones.
The css:
<style>
.msgIcon {
display: none!important
}
.customMessage * {
color: #fff!important
}
.customMessage {
margin: 5px 0!important;
max-width: 1280px;
opacity: 1!important;
width: 100%;
font-size: 12px;
border: 0px;
padding-left: 10px;
}
.message {
opacity: .1
}
</style>
The Javascript code:
<script>
$(document).ready(function(){
overridePageMessages();
});
function overridePageMessages(){
var textureEffect = '';
//Uncomment below line for texture effect on page messages
//textureEffect = 'slds-theme--alert-texture';
$('.warningM3').addClass('slds-notify slds-notify--toast slds-theme--warning customMessage '+textureEffect);
$('.confirmM3').addClass('slds-notify slds-notify--alert slds-theme--success customMessage '+textureEffect);
$('.errorM3').addClass('slds-notify slds-notify--alert slds-theme--error customMessage '+textureEffect);
$('.infoM3').addClass('slds-notify slds-notify--toast customMessage '+textureEffect);
$('.errorM3').removeClass('errorM3');
$('.confirmM3').removeClass('confirmM3');
$('.infoM3').removeClass('infoM3');
$('.warningM3').removeClass('warningM3');
}
</script>
Add the script to document.ready() or the oncomplete function.