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()" />