on 2023 Aug 04 1:37 PM
Hi,
I've noticed that starting from SAPUI5 1.115 the sap.ui.core.message.MessageManager is becoming a singleton and creating own instances of it is deprecated. We've used multiple instances of the MessageManager in our project for mainly one reason, to have different instances of the MessageModel to be able to have a bit of separation of concerns.
For instance, since the sap.ui.getCore().getMessageManager() will contain error messages for failed oData requests and we are often clearing all messages before doing a $batch request for error handling, we used a new instance of the MessageManager to handle field validation on a creation screen, which will always contain messages only related to that without having the risk or need of clearing them. Another example where two message models would be handy is to do field validations inside a dialog on a creation screen, so the error message count in the footer of the page doesn't increase due to messages inside the dialog and also since there is a model that contains only messages related to the dialog, you don't have to check each control individually.
Could you help me with an advice on this topic? Is this just a bad pattern/thing to do and what could we do instead?
Request clarification before answering.
I was exploring the same problem.. I created a helper class to achieve this segregation of messages. It might help others 🙂
Feel free to share your feedback, positive/ negative...
P.S.: - Wasn't able to test all the scenarios...
//Heler class
import Button from "sap/m/Button";
import { ButtonType } from "sap/m/library";
import MessageItem from "sap/m/MessageItem";
import MessagePopover, { MessagePopover$ActiveTitlePressEvent } from "sap/m/MessagePopover";
import ManagedObject from "sap/ui/base/ManagedObject";
import ElementRegistry from "sap/ui/core/ElementRegistry";
import Message from "sap/ui/core/message/Message";
import MessageProcessor from "sap/ui/core/message/MessageProcessor";
import Messaging from "sap/ui/core/Messaging";
import ClientListBinding from "sap/ui/model/ClientListBinding";
import Filter from "sap/ui/model/Filter";
import FilterOperator from "sap/ui/model/FilterOperator";
//types
type fnGroupNameType = ((message: Message) => string | undefined) | undefined;
type constructorType = {
filters?: Filter[];
fnGroupName?: fnGroupNameType;
} | undefined;
type removeMessagesType = {
target: string;
isTargetAbsolute?: boolean | undefined;
processor?: MessageProcessor
}
/**
* @namespace messagingts.utils
*/
export default class MessagingHelper extends ManagedObject {
private filters: Filter[];
private messsagePopover: MessagePopover;
private messageButton: Button;
private messageModelName: string;
private fnGroupName: fnGroupNameType;
constructor(params?: constructorType) {
super();
this.messageModelName = `${this.getId()}_messageModel`; // randomizing model name to avoid conflicts
this.getMessagePopover(); // initialize the message popover
this.setMessageFilters(params?.filters ?? []); // set the filters for the message popover
this.setMessageGroupFormatter(params?.fnGroupName); // set the group name formatter function
this.getMessagebutton(); // initialize the message button
}
setMessageFilters(filters: Filter[] | undefined): void {
const messagelist = this.getMessagePopover().getBinding("items") as ClientListBinding;
messagelist.filter(Array.isArray(filters)? new Filter({ filters, and: false }) : filters);
}
setMessageGroupFormatter(fnGroupName: fnGroupNameType): void {
this.fnGroupName = fnGroupName;
}
getMessagePopover(): MessagePopover {
if (!this.messsagePopover) {
this.messsagePopover = new MessagePopover({
activeTitlePress: (event: MessagePopover$ActiveTitlePressEvent) => { this.onActiveTitlePress(event); },
items: {
path: `${this.messageModelName}>/`,
template: new MessageItem({
title: `{${this.messageModelName}>message}`,
subtitle: `{${this.messageModelName}>additionalText}`,
groupName: {
path: `{${this.messageModelName}>}`,
formatter: (message: Message) => { return this.getGroupeName(message); }
},
activeTitle: {
path: `${this.messageModelName}>controlIds`,
formatter: (controlIds: string[]) => { return controlIds.length > 0; }
},
type: `{${this.messageModelName}>type}`,
description: `{${this.messageModelName}>description}`,
})
}
})
this.messsagePopover.setModel(Messaging.getMessageModel(), this.messageModelName)
const itemBinding = this.messsagePopover.getBinding("items") as ClientListBinding;
itemBinding.attachChange((event: { getSource: () => ClientListBinding }) => { this.onMessageChange(event); });
}
return this.messsagePopover;
}
getMessagebutton(): Button {
this.messageButton ??= new Button({
visible: false,
icon: "sap-icon://information",
type: ButtonType.Neutral,
press: () => { this.messsagePopover.openBy(this.messageButton); },
ariaHasPopup: "Dialog"
});
return this.messageButton;
}
toggleMessageButton(open: boolean = true): void {
setTimeout(() => {
open ? this.messsagePopover.openBy(this.messageButton) : this.messsagePopover.close();
}, 10);
}
addMessages(messages: Message[] | Message): void {
Messaging.addMessages(messages)
}
removeMessages(params: removeMessagesType[] | removeMessagesType) {
params = Array.isArray(params) ? params : [params];
const filters: Filter[] = params.map((param) => {
const filters: Filter[] = [];
filters.push(new Filter("target", param.isTargetAbsolute ? FilterOperator.EQ : FilterOperator.StartsWith, param.target));
param?.processor && filters.push(new Filter("processor", FilterOperator.EQ, param.processor));
return new Filter({ filters: filters, and: true });
});
const listBinding = Messaging.getMessageModel().bindList("/");
listBinding.filter(new Filter({ filters: filters, and: false }));
const messages = listBinding.getAllCurrentContexts().map((context) => context.getObject() as Message);
listBinding.destroy();
Messaging.removeMessages(messages);
}
removeAllMessages(): void {
const listBinding = this.messsagePopover.getBinding("items") as ClientListBinding;
const messages = listBinding.getAllCurrentContexts().map((context) => context.getObject() as Message);
Messaging.removeMessages(messages);
}
private onActiveTitlePress(event: MessagePopover$ActiveTitlePressEvent): void {
const message = event.getParameters().item?.getBindingContext(this.messageModelName)?.getObject() as Message;
const control = ElementRegistry.get(message.getControlId());
control?.focus();
}
private onMessageChange(event: { getSource: () => ClientListBinding; }): void {
const listBinding = event.getSource();
const contexts = listBinding.getAllCurrentContexts();
const button = this.getMessagebutton();
let icon = "sap-icon://information";
let highestSeverity: ButtonType = ButtonType.Neutral;
contexts.forEach((context) => {
let message = context.getObject() as Message;
switch (message.getType()) {
case "Error":
icon = "sap-icon://error";
highestSeverity = ButtonType.Negative;
break;
case "Warning":
icon = icon !== "sap-icon://error" ? "sap-icon://alert" : icon;
highestSeverity = highestSeverity !== ButtonType.Negative ? ButtonType.Critical : highestSeverity;
break;
case "Success":
icon = icon !== "sap-icon://error" && icon !== "sap-icon://alert" ? "sap-icon://sys-enter-2" : icon;
highestSeverity = highestSeverity !== ButtonType.Negative && highestSeverity !== ButtonType.Critical ? ButtonType.Success : highestSeverity;
break;
default:
icon = !icon ? "sap-icon://information" : icon;
highestSeverity = !highestSeverity ? ButtonType.Neutral : highestSeverity;
break;
}
});
button.setVisible(contexts.length > 0);
button.setIcon(icon);
button.setType(highestSeverity);
button.setText(contexts.length.toString());
}
private getGroupeName(message: Message): string | undefined {
if (this.fnGroupName) {
return this.fnGroupName(message);
}
}
}
// Controller
public onInit(): void {
this.getView()?.setModel(new JSONModel(), "viewModel");
const page = this.getView()?.byId("page") as Page; // can be any container e.g. footer
this.messagingHelper = new MessagingHelper();
page.insertContent(this.messagingHelper.getMessagebutton(), 0);
// setting filter for view's children
this.messagingHelper.setMessageFilters([new Filter("target", FilterOperator.StartsWith, this.getView()?.getId())]);
// or
//setting filter for view's children & manually added messages with binding path and model
this.messagingHelper.setMessageFilters([
new Filter("target", FilterOperator.StartsWith, this.getView()?.getId()),
new Filter({
filters: [
new Filter("target", FilterOperator.StartsWith, "/Propety"),
new Filter("processor", FilterOperator.EQ, this.getView()?.getModel("viewModel")),
]
})
]);
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
72 | |
21 | |
8 | |
7 | |
6 | |
6 | |
5 | |
4 | |
4 | |
3 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.