cancel
Showing results for 
Search instead for 
Did you mean: 

SAPUI5, Creating own instances of MessageManager deprecated from 1.115

florin1335
Explorer
650

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?

View Entire Topic
PB26
Newcomer
0 Kudos

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")),
            ]
        })
    ]);
    
}