# Finishing Touches
###### tags: `module-tutorial` `foundry-vtt`
We could stop here, we have a pretty solid module and we've touched on the following aspects of the Foundry VTT API:
- Manifest JSON
- Hooks
- Localization
- Flags
- FormApplication
- Handlebars
- CSS
But there are two more useful things to learn about in the Foundry VTT API and they happen to let us solve some minor problems with our to-do list module:
- Settings
- Dialog
## I. Settings
Foundry provides us with an API to store and retrieve data that isn't tied to a specific Document by way of [Settings](https://foundryvtt.wiki/en/development/api/settings). We chose to put our ToDos on the `User` document with Flags and in doing so we made the decision that "ToDos relate to the `User` document."
Suppose we want to allow an individual user to not see the ToDoList button in their user list. We can define a setting and use that setting to drive the injection.
### Define a SETTINGS constant
Similar to what we did with `ToDoList.FLAGS`, we should define `ToDoList.SETTINGS`. This is an optional quality of life thing, but it can help us prevent typos.
In your `ToDoList` class, add the following:
```js=
static SETTINGS = {
INJECT_BUTTON: 'inject-button'
}
```
### Registering a Setting
Next we need to add actually [register the setting](https://foundryvtt.com/api/ClientSettings.html#register) with Foundry's API during our module's initialization.
Add the following to the `ToDoList.initialize` method:
```js=
game.settings.register(this.ID, this.SETTINGS.INJECT_BUTTON, {
name: `TODO-LIST.settings.${this.SETTINGS.INJECT_BUTTON}.Name`,
default: true,
type: Boolean,
scope: 'client',
config: true,
hint: `TODO-LIST.settings.${this.SETTINGS.INJECT_BUTTON}.Hint`,
});
```
A lot is going on here so let's break it down one piece at a time.
#### `game.settings.register...`
This is a core method needed to register the setting. It takes in three arguments:
1. Module ID (which we defined a constant for)
2. Setting ID (which we also defined a constant for)
3. 'options' (which has a lot of stuff inside it)
#### `name` & `hint`
These are the text displayed to the user in the settings panel. We could simply pass in a string, but that's not localization-friendly. Instead, we pass in a localization key, which Foundry Core then uses automatically.
Incidentally, this means we need to add this to our `en.json`:
```json=
{
"TODO-LIST": {
...
"settings": {
"inject-button": {
"Name": "Display To-Do List button?",
"Hint": "Controls whether or not the To-Do List can be opened from the player list."
}
}
}
}
```
#### `default`
Controls the default value. We want this to be `true` by default.
#### `type`
Tells Foundry Core this is a boolean value (as opposed to a String or Object).
#### `scope`
There are two scopes: `world` and `client`. World scoped settings can only be changed by the GM and affect every client at once, client scoped settings are editable by each individual and each person's selection affects only their machine.
Importantly, client scoped settings can't be seen by other clients.
#### `config`
This tells Foundry that this particular setting should show up in the configuration menu.
### Test!
Refresh, then go to Configure Settings > Module Settings and you should see your module's settings there.

### Using the setting
Now that we have registered a setting, we can `get` it in the `renderPlayerList` hook and prevent the button's insertion if the value is 'false'.
```js=
Hooks.on('renderPlayerList', (playerList, html) => {
if (!game.settings.get(ToDoList.ID, ToDoList.SETTINGS.INJECT_BUTTON)) {
return;
}
// ...
```
### Test!
Refresh, open your module settings un-check the "Display To-Do List button?" setting, then save.
> ***Narrator:*** The button's still there.
Since we've already injected the button, the act of setting the setting doesn't usually force a re-render of the Player List. If you click on the heading of the Player List (to expand it), you'll see the button disappear.
Likewise if you have the setting off but turn it on, the same problem affects us.
### Setting Callbacks
We can pass a callback to the registered setting with the `onChange` option:
```
game.settings.register(this.ID, this.SETTINGS.INJECT_BUTTON, {
// ...
onChange: () => ui.players.render()
```
In this callback, we're tapping into Foundry Core's `PlayerList#render` method to force it to re-render when the setting's value changes. This is very similar to what we do in our own `ToDoListConfig#_handleButtonClick`, except we have to get the instance of the Application from `ui.players`.
### One more test!
Once more, refresh, then open Settings and toggle the setting. Now the button appears and disappears correctly as the setting changes!
## II. Dialog
Make sure you have that INJECT_BUTTON setting toggled on for this part.
A [`Dialog`](https://foundryvtt.wiki/en/development/api/dialog) is a sibling of the [`FormApplication`](https://foundryvtt.com/api/FormApplication.html) in that they both are part of the [`Application`](https://foundryvtt.com/api/Application.html) family. Where the FormApplication excells at handling complex data and live editing, the Dialogs excells at much simpler use-cases.
Let's say we don't like that Deleting a ToDo happens instantly and we'd rather have a confirmation dialog pop up when someone wants to delete one.
### `Dialog.confirm`
That's where the [`Dialog.confirm`](https://foundryvtt.com/api/Dialog.html#.confirm) factory method comes in handy. This is a simple way to prompt a user about an action without having to set up a lot of overhead.
:::info
**Programming Concept: Factory Method**
Simply put, a "factory method" is a utility function which pre-configures a very common task.
In this case, the `Dialog` class allows you to make fully custom Dialogs, but "yes/no" prompts are so common that Foundry Core has defined a special short-cut to create those.
:::
In `ToDoListConfig#_handleButtonClick` change the `delete` case to look like this:
```js=
case 'delete': {
const confirmed = await Dialog.confirm({
title: game.i18n.localize("TODO-LIST.confirms.deleteConfirm.Title"),
content: game.i18n.localize("TODO-LIST.confirms.deleteConfirm.Content")
});
if (confirmed) {
await ToDoListData.deleteToDo(toDoId);
this.render();
}
break;
}
```
The neat thing about `Dialog.confirm` is that it can be `await`ed and that will only continue forward when the user selects a choice. It also returns a `boolean` with the user's choice, where `true` means "yes".
We again are using `game.i18n` to ensure our prompts are localized, which means we need some additions to our `en.json` file:
```json=
{
"TODO-LIST": {
...
"confirms": {
"deleteConfirm": {
"Title": "Confirm Deletion",
"Content": "Are you sure you want to delete this To-Do? This action cannot be undone."
}
}
}
}
```
### Test!
Refresh, open the ToDoListConfig, and try to delete a ToDo. The prompt will appear and your choice will determine if the ToDo is actually deleted.
<details>
<summary>Success!</summary>

</details>
## III. Wrapping Up
Some finishing touches were applied in this section, we:
- Registered a module setting with Foundry
- Used that setting's value to decide if we should inject our button
- Learned about `Dialog` and `Dialog.confirm`
- Used a `Dialog.confirm` to confirm deletion of ToDos
<details>
<summary>Final `ToDoList` class</summary>
```js=
class ToDoList {
static ID = 'todo-list';
static FLAGS = {
TODOS: 'todos'
}
static TEMPLATES = {
TODOLIST: `modules/${this.ID}/templates/todo-list.hbs`
}
static SETTINGS = {
INJECT_BUTTON: 'inject-button'
}
/**
* A small helper function which leverages developer mode flags to gate debug logs.
*
* @param {boolean} force - forces the log even if the debug flag is not on
* @param {...any} args - what to log
*/
static log(force, ...args) {
const shouldLog = force || game.modules.get('_dev-mode')?.api?.getPackageDebugValue(this.ID);
if (shouldLog) {
console.log(this.ID, '|', ...args);
}
}
static initialize() {
this.toDoListConfig = new ToDoListConfig();
game.settings.register(this.ID, this.SETTINGS.INJECT_BUTTON, {
name: `TODO-LIST.settings.${this.SETTINGS.INJECT_BUTTON}.Name`,
default: true,
type: Boolean,
scope: 'client',
config: true,
hint: `TODO-LIST.settings.${this.SETTINGS.INJECT_BUTTON}.Hint`,
onChange: () => ui.players.render()
});
}
}
```
</details>
<details>
<summary>Final `renderPlayerList` hook</summary>
```js=
Hooks.on('renderPlayerList', (playerList, html) => {
// if the INJECT_BUTTON setting is false, return early
if (!game.settings.get(ToDoList.ID, ToDoList.SETTINGS.INJECT_BUTTON)) {
return;
}
// find the element which has our logged in user's id
const loggedInUserListItem = html.find(`[data-user-id="${game.userId}"]`)
// create localized tooltip
const tooltip = game.i18n.localize('TODO-LIST.button-title');
// insert a button at the end of this element
loggedInUserListItem.append(
`<button type='button' class='todo-list-icon-button flex0' title="${tooltip}">
<i class='fas fa-tasks'></i>
</button>`
);
// register an event listener for this button
html.on('click', '.todo-list-icon-button', (event) => {
const userId = $(event.currentTarget).parents('[data-user-id]')?.data()?.userId;
ToDoList.toDoListConfig.render(true, { userId });
});
});
```
</details>
<details>
<summary>Final `ToDoListConfig#_handleButtonClick`</summary>
```js=
async _handleButtonClick(event) {
const clickedElement = $(event.currentTarget);
const action = clickedElement.data().action;
const toDoId = clickedElement.parents('[data-todo-id]')?.data()?.todoId;
ToDoList.log(false, 'Button Clicked!', { this: this, action, toDoId });
switch (action) {
case 'create': {
await ToDoListData.createToDo(this.options.userId);
this.render();
break;
}
case 'delete': {
const confirmed = await Dialog.confirm({
title: game.i18n.localize("TODO-LIST.confirms.deleteConfirm.Title"),
content: game.i18n.localize("TODO-LIST.confirms.deleteConfirm.Content")
});
if (confirmed) {
await ToDoListData.deleteToDo(toDoId);
this.render();
}
break;
}
default:
ToDoList.log(false, 'Invalid action detected', action);
}
}
```
</details>
<details>
<summary>Final `en.json`</summary>
```json=
{
"TODO-LIST": {
"button-title": "Open To-Do list",
"add-todo": "Add To-Do",
"delete-todo": "Delete To-Do",
"mark-done": "Mark Done",
"settings": {
"inject-button": {
"Name": "Display To-Do List button?",
"Hint": "Controls whether or not the To-Do List can be opened from the player list."
}
},
"confirms": {
"deleteConfirm": {
"Title": "Confirm Deletion",
"Content": "Are you sure you want to delete this To-Do? This action cannot be undone."
}
}
}
}
```
</details>
Next Step: [Conclusion](/cLZBTZQpTXi1t6yGtzVF_w)
{%hackmd io_aG7zdTKyRe3cpLcsxzw %}