npm package: @prp-apps-solutions/ui5-message-handler
Description: A package including utils to handle messages in UI5 applications.
Author: pr:p apps & solutions
Latest version: 1.2.0

# ui5-message-handler

This packages serves as a lightweight and simple message handling utility using JSONModels and the [MessageBox](https://sdk.openui5.org/api/sap.m.MessageBox). The main features are custom message handling as well as error handling.

Further controls are provided, e.g., a simple SelectionDialog to enable a user to select a key-value-pair using a dropdown and displaying a text area with predefined content based on the selection/ enabling a user to write custom text to a selected key.

# Installation
- NPM: `npm install @prp-apps-solutions/ui5-message-handler`
- PNPM: `pnpm add @prp-apps-solutions/ui5-message-handler`

# Description & Usage

The error handler and the message handler can be used in your application in parallel. Both handle the messages/errors in a similar way by

- displaying them in a MessageBox
- adding them to a global error model/ one of your message models
- enabling further control handling by providing optional methods for method chaining

The SelectionDialog is another possibility to display information, handle custom actions or enforce users to provide more context for a specific selection.

## Error Handler
All errors are added to an error model by default. The error model's name is `errorModel`. If you want to give the model a different name, provide the `modelName` property of the error handler instance on initial instantiation.

:bulb: The following usage descriptions contain examples only which may differ from your actual usage.

### (Optional) Error Handler - Place Button, MessageStrip and add MessagePopover

You can optionally add a button, a message strip and a message popover to directly display your error messages: 

- The button opens the message popover on press while showing the amount of errors as button text. 
- The message strip displays the latest error message.
- The message popover displays the details of the errors added to the error model.

#### Example Usage

Place the controls within a toolbar in the init method of your corresponding controller:

```typescript
// Main.controller.ts
class Main extends Controller {
    public onInit(): void {
		ErrorMessageControlHelper.getInstance().placeErrorMessageStrip(
			this.getView().byId("MyToolbarId")
		);

        // ...
    }

    // ...
}

```

The final result may look like this (if an error occurred):

![Button Message Strip example](./documents/images/button_messagestrip_example.png)

### Error Handler – Usage
It is highly recommended to initialize the error handler in your component. Therefore, set a global error handler which can be accessed from all your controllers across your application. Further – if you use a base controller from which all your other controllers inherit –, a convenience method to get this error handler in your base class is also recommended.

- First instantiation in your `Component.ts`:

```typescript
export default class Component extends UIComponent {

    // if not passing the second parameter, the model's name will be "errorModel"
    private _errorHandler: ErrorHandler = new ErrorHandler(this, "myErrorModel");

	public init(): void {
		// call the base component's init function
		super.init();

		// create the error model with the default name
        this.setModel(models.createErrorModel(), this._errorHandler.getModelName());

		// create the views based on the url/hash
		this.getRouter().initialize();
	}

    // ...
}
```
- (Optional) Convenience method in your BaseController:
```typescript
export default class BaseController extends Controller {
    // ...
    protected getErrorHandler(): ErrorHandler {
		const errorHandler = this.getOwnerComponent().getErrorHandler();
		return errorHandler;
	}

    // ...
}
```

- Usage in one of your controller – Odata request failed:
```typescript

// handle error only
this.getODataModel().create("/<anyPath>", payload, {
    success: (response: any) => {
        // do sth.
    },
    error: (error: OdataError) => {
        // get the error handler and add message to the error model
        this.getErrorHandler().handleError(error);
    },
});

// show custom error message
this.getODataModel().create("/<anyPath>", payload, {
    success: (response: any) => {
        // do sth.
    },
    error: (error: OdataError) => {
        // get the error handler, add message to the error model, and display the custom message
        this.getErrorHandler().showCustomError("My custom error message.", true, {
            title: "Watch out for my error message",
        });
    },
});

// optional method chaining example
this.getODataModel().create("/<anyPath>", payload, {
    success: (response: any) => {
        // do sth.
    },
    error: (error: OdataError) => {
        // any control that's busy state should be set to false
        const control = this.getView();
        
        // any dialog
        const dialog = this.getView().byId("myDialogId")

        // get the error handler and add message to the error model
        // 1. set a controls busy state to false
        this.getErrorHandler().handleError(error).setBusyFalse(control);

        // 2. set busy state of dialog to false and close it.
        //    Attention: closeDialog() is only available, if the control passed to setBusyFalse is a dialog
        this.getErrorHandler().handleError(error).setBusyFalse(dialog).closeDialog();

        // 3. close any dialog
        this.getErrorHandler().handleError(error).closeDialog(dialog);
    },
});
```

## Message Handler
The message handler can be used to display a message box of the following types displaying the corresponding icons and buttons:

- alert
- information
- confirm
- success
- warning

It is also possible to add your messages to a JSONModel for further processing or only adding it to a model.

### Message Handler - Usage

- Create a new instance in your controller or Component
```typescript
// without message model
// controller
private _messageHandler = new MessageHandler(this.getOwnerComponent());
// component
private _messageHandler = new MessageHandler(this);

// with message model
private _messageHandler = new MessageHandler(this, "myMessageModel");

```

- Create a model for your handler (if not initially done)
```typescript
// example in your controller with default name 'messageModel'
this._messageHandler.createMessageModel();

// example in your controller with custom model name
this._messageHandler.createMessageModel("myMessageModel");

```

- Show custom message
```typescript
// show message
this._messageHandler.showCustomMessage({
	title: "My Custom Title",
	message: "My beautiful message to be displayed",
	messageType: "information"
});

// show message and add to your model – if model exists
this._messageHandler.showCustomMessage({
	title: "My Custom Title",
	message: "My beautiful message to be displayed",
	messageType: "information"
}).addToModel();
```

- Add message to model only
```typescript
this._messageHandler
    .setMessage("information", "My beautiful message to add to my model")
    .addToModel();
```

## SelectionDialog
The SelectionDialog can be used to display any key-value-pair in a dropdown (see [Select](https://ui5.sap.com/#/api/sap.m.Select)). Further, a [TextArea](https://ui5.sap.com/#/api/sap.m.TextArea) is provided which can be used for the following cases:

- Displaying further information to the selected key of the dropdown.
- Displaying pre-defined texts - based on a selected key - and enable a client to change the text before submitting it.
- Using the TextArea to provide an option for clients to submit a text to a selected key.

### SelectionDialog - Hints

> :bulb: **Hints**
> When passing the data to be displayed as a [JSONModel](https://ui5.sap.com/#/api/sap.ui.model.json.JSONModel), you must provide an array of objects which (at least) have the following properties:
> - `key`: The key used for the Select control.
> - `description`: The value to be displayed as additional text when the dropdown (Select control) is used.
> - `detail`: The text to be displayed in the TextArea (if `undefined`, the area is set to '--' by default).
>
> Moreover, 
> - the event handler of the provided close button is provided and destroys it to prevent duplicate id errors.
> - the selection change event handler of the Select control is implemented and sets the TextArea's value (`model>detail`) based on the selected key.

### SelectionDialog - Usage

- Create a new instance where needed
```typescript
const selectionDialog = SelectionDialog.getInstance();
```

- Open the SelectionDialog using the `openSelectionDialog` method within a button press event handler:
```typescript

// map your data to the appropriate structure as defined in the type ResultType
const mapped = {
    results: data.results.map((res) => {
        return {
            key: res.myKey,
            description: res.myDescription,
            detail: res.myDetail,
        };
    }),
};

// create and open the SelectionDialog
this.selectionDialogInstance = SelectionDialog.getInstance();
const { select, textArea }= await selectionDialog.openSelectionDialog(
    event, // Button$PressEvent
    {
        modelData: mapped,
        modelName: "myModelName",
		calculateWidths: true,
		selectDialog: {
			selectDialogTitle: "MyTitle",
		}
		select: {
			selectLabel: "My select label",
			selectColumnRatio: "1:2",
		}
        textArea: {
            editable: true,
            rows: 10,
            textAreaLabel: "My text area label",
        },
        button: {
            cancelButton: {
                text: "Cancel",
                type: ButtonType.Default,
            },
            confirmButton: {
                text: "Confirm",
                type: ButtonType.Accept,
                press: this.handleConfirmButtonPress.bind(this), // bind your own logic for a positive action
            },
        },
    },
    this.getView() // view to add the dialog to
);

// access data
const selectedKey = select.getSelectedKey();
const text = textArea.getValue();

// close the dialog
this.selectionDialogInstance.closeDialog();
```

#### `openSelectionDialog` – parameters

##### event

The corresponding Button press event on which the dialog should be opened.

##### options	 

```typescript
/**
 * Minimal set of general properties to display
 * data in the {@link SelectionDialog}.
 */
type ResultType = {
	/**
	 * Key used to be bound to the
	 * Select control of the SelectionDialog.
	 */
	key: string;

	/**
	 * Description that is displayed when using
	 * the Select control.
	 */
	description: string;

	/**
	 * Detail property to display text in the Dialog's
	 * TextArea control.
	 *
	 * @remarks
	 * If no text should be displayed on selection change,
	 * provide the property as `detail: undefined` in your model.
	 */
	detail: string;
};
```

```typescript
/**
 * Options to provide for the SelectionDialog.
 */
export interface SelectDialogOptions<T extends ResultType> {
	/**
	 * The name of the model to be bound
	 * to the Selection dialog.
	 */
	modelName: string;

	/**
	 * Data to be bound to the JSONModel
	 * of the SelectionDialog.
	 *
	 * @remarks
	 * The Model must be provided as an object
	 * containing the actual data as an array.
	 */
	modelData: {
		/**
		 * An array of data to be displayed,
		 * see {@link ResultType}.
		 */
		results: T[];
	};

	selectDialog: {
		/**
		 * Title property of the Dialog control.
		 *
		 * See `title` property of [Dialog](https://ui5.sap.com/#/api/sap.m.Dialog%23controlProperties).
		 */
		selectDialogTitle: string;

		/**
		 * The content width of the {@link Dialog}.
		 *
		 * See `contentWidth` property of [Dialog](https://ui5.sap.com/#/api/sap.m.Dialog%23controlProperties).
		 */
		selectDialogContentWidth?: string;

		/**
		 * Optional indicator whether the dialog and it's control widths should be calculated based
		 * on the user's screen width and the select items.
		 *
		 * @remarks
		 * If provided as `true`, all provided widths are ignored.
		 */
		calculateWidths?: boolean;
	};

	select: {
		/**
		 * Text that is displayed above
		 * the Select control within the SelectionDialog.
		 *
		 * @remarks
		 * Provide a short description of what the client
		 * can choose from.
		 */
		selectLabel: string;

		/**
		 * Determines the column ratio when displaying the key and an additional text
		 * for the {@link Select}.
		 *
		 * @remarks
		 * Pattern must follow: 1:1, 1:2, 2:1, 3:2, etc.
		 * Default value is 1:1.
		 */
		selectColumnRatio?: string;

		/**
		 * The width of the select field.
		 * For more information see [sap.m.Select#Properties](https://sapui5.hana.ondemand.com/#/api/sap.m.Select%23controlProperties).
		 */
		selectWidth?: string;
	};

	/**
	 * Options of the TextArea control.
	 */
	textArea: {
		/**
		 * Text to be displayed above the TextArea
		 * control.
		 *
		 * @remarks
		 * Provide a short description of what is displayed/
		 * should be entered in the TextArea.
		 */
		textAreaLabel: string;

		/**
		 * Indicator whether the TextArea can be edited.
		 *
		 * See [TextArea](https://ui5.sap.com/#/api/sap.m.TextArea).
		 */
		editable: boolean;

		/**
		 * Row property of the TextArea to render
		 * at least the provided number of rows.
		 *
		 * See [TextArea](https://ui5.sap.com/#/api/sap.m.TextArea).
		 */
		rows: number;
	};

	/**
	 * Options of the dialog's buttons aggregation.
	 *
	 * @remarks
	 * Currently, the Dialog is limited to two buttons.
	 */
	button: {
		/**
		 * Button options for the 'confirm' button, i.e., a button
		 * invoking a 'positive' action on press.
		 */
		confirmButton: {
			/**
			 * {@link ButtonType} that determines
			 * the style of the button.
			 *
			 * See [ButtonType](https://ui5.sap.com/#/api/sap.m.ButtonType).
			 */
			type: ButtonType;

			/**
			 * Text to be displayed within the Button.
			 *
			 * See [Button](https://ui5.sap.com/#/api/sap.m.Button%23controlProperties).
			 */
			text: string;

			/**
			 * Function to be invoked when the 'confirm button'
			 * of the SelectionDialog is pressed.
			 *
			 * @param event - The button's press event.
			 */
			press: (event: Button$PressEvent) => void | Promise<void>;

			/**
			 * Optional custom property as aggregation to the button.
			 * Can be used to save a key for further processing on button
			 * press.
			 */
			customKey?: string;
		};

		/**
		 * Cancel button options.
		 *
		 * @remarks
		 * The close method is provided out the box,
		 * no implementation needed.
		 */
		cancelButton: {
			/**
			 * {@link ButtonType} that determines
			 * the style of the button.
			 *
			 * See [ButtonType](https://ui5.sap.com/#/api/sap.m.ButtonType).
			 */
			type: ButtonType;

			/**
			 * Text to be displayed within the Button.
			 *
			 * See [Button](https://ui5.sap.com/#/api/sap.m.Button%23controlProperties).
			 */
			text: string;
		};
	};
}
```

##### view
The view of the calling application to which the SelectionDialog should be added.