Using Bootstrap Modal in Svelte

Using Bootstrap Modal in Svelte

Bootstrap offers an modal which will have the styles needed to show the popup. We can use that in our svelte application to make the styling of the component easier. We will implement it as a separate component which can be reused throughout application.

Using Bootstrap Modal in Svelte

Introduction

Modal is a popup which shows for the user to either inform the user or provide some actions for the user to take. Modals should be displayed when the user action is needed before proceeding to the next step. Since modal are displayed over the page and interrupt the user flow, it needs to be used carefully so as to not annoy the user with many popup.

Having said all of this, Modals are a common pattern followed in modern web design to get confirmation from the user before performing an action. When delete button is pressed and the delete action is permanent, it is often a good practice to confirm the action with the user to ensure the action is not a mistake.

Bootstrap Modal

Bootstrap offers an modal which will have the styles needed to show the popup. We can use that in our svelte application to make the styling of the component easier. We will implement it as a separate component which can be reused throughout application.

If you want to learn about setting up bootstrap in your application, follow this tutorial

https://www.eternaldev.com/blog/using-bootstrap-with-svelte/

Example of Modal

example.png

Creating a Modal component

After setting up you svelte project, let’s create a new file and call it Modal.svelte

This component will receive a few props from the parent component

  1. open - Show/Hide the popup
  2. showBackdrop - Whether to show the black backdrop or not
  3. onClosed - Function to update the parent component when the popup is closed

We can add more props when needed to further customise the component

<script>
  export let open = false;
  export let showBackdrop = true;
  export let onClosed;

  const modalClose = () => {
    open = false;
    if (onClosed) {
      onClosed();
    }
  }

</script>

We are exporting the three properties which we discussed above and then we have a function modalClose which will be used to close the popup when the close/cancel button is pressed.

{#if open}
  <div class="modal" id="sampleModal" tabindex="-1"
    role="dialog" aria-labelledby="sampleModalLabel" aria-hidden={false}
  >
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="sampleModalLabel">Modal title</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"
            on:click={modalClose}
          >
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">...</div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal" on:click={modalClose}
            >Close</button>
          <button type="button" class="btn btn-primary">Save changes</button>
        </div>
      </div>
    </div>
  </div>
  {#if showBackdrop}
    <div class="modal-backdrop show" />
  {/if}
{/if}

<style>
  .modal {
    display: block;
  }
</style>

Most of the code above is directly from the bootstrap modal example :) The main thing to note here is the if condition. We are showing the popup only when the open prop is true. We are adding an extra style to the modal class to set it to display:block since the bootstrap default hides the popup.

We are adding on:click handlers to both the Close button and X button at the top of the modal to the same function modalClose which will set the open prop to false and call the callback function of the parent.

All of the other classes are added for styling purpose and can be found in bootstrap modal page.

Using the Modal component

Let’s update the App.svelte to use the new Modal component to show the popup.

<script>
	import Modal from "./Modal.svelte";

	let showPopup = false;

	const onCancelClick = (ev) => {
		showPopup = true;
	}

	const onPopupClose = () => {
		showPopup = false;
	}
</script>

<main>
	<div class="container">
		<button type="button" class="btn btn-success" on:click={onShowPopup}>
			Show Popup
		</button>
		<Modal open={showPopup} onClosed={() => onPopupClose()}/>
	</div>
</main>

We are defining one variable which will hold the status of the popup display which is showPopup. On the button click, the variable is update to true which will show the popup. When the close button is clicked in the popup, onPopupClose method is called which updates the variable to false

We now have a simple popup which can be reused in the application.

Return data when the Modal is closed

Since the modal can contain multiple actions for the user to select, you would need a way to know which action was selected. So let’s update the component to return that data.

<script>
	...
	const modalClose = (data) => {
    open = false;
    if (onClosed) {
      onClosed(data);
    }
  }
</script>

...
...
<div class="modal-footer">
    <button type="button" class="btn btn-secondary" data-dismiss="modal" 
			on:click={() => modalClose('close')}>Close</button>
    <button type="button" class="btn btn-primary" 
			on:click={() => modalClose('save')}>Save changes</button>
</div>
...
...

Update the action buttons in the modal to call the modalClose with some string data. You can make it into some complex object as well. So when calling the parent component, we are passing this data to the parent.

App.svelte

<script>
	...
	const onPopupClose = (data) => {
		showPopup = false;

		console.log(data);
	}
</script>

Adding Animations to Svelte

The modal will benefit a lot by adding some simple animations to show and hide the modal. This can be achieved using the svelte animation properties

<script>
  import { fade, fly } from "svelte/transition";
  import { quintOut } from "svelte/easing";
</script>

{#if open}
  <div class="modal" id="sampleModal" tabindex="-1"
    role="dialog" aria-labelledby="sampleModalLabel" aria-hidden={false}
  >
    <div class="modal-dialog" role="document" 
			in:fly={{ y: -50, duration: 300 }}
	    out:fly={{ y: -50, duration: 300, easing: quintOut }}>

      ........
    </div>
  </div>
  {#if showBackdrop}
    <div class="modal-backdrop show" transition:fade={{ duration: 150 }} />
  {/if}
{/if}

Import the fade and fly animation from svelte. We can add it to the modal dialog div and so when it gets shown in the DOM, in: animation is played. When it gets hidden in the DOM, out: animation is played. We can set the animation duration and position to the animation.

Adding title and content props

Modal.svelte

<script>
	...
  export let open = false;
  export let showBackdrop = true;
  export let onClosed;

  export let title = 'Modal title';
	...
</script>

<div class="modal-header">
  <h5 class="modal-title" id="sampleModalLabel">{title}</h5>
  <button type="button" class="close" data-dismiss="modal" aria-label="Close" on:click={() => modalClose('close')}>
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  <slot></slot>
</div>
<div class="modal-footer">
  <button type="button" class="btn btn-secondary" data-dismiss="modal" on:click={() => modalClose('close')}>Close</button>
  <button type="button" class="btn btn-primary" on:click={() => modalClose('save')}>Save changes</button>
</div>

Now we are exposing the title prop to the parent. We can change the title of the modal. We are also adding <slot> in the modal body. Now we can pass the HTML which needs to be shown in the modal from the parent component.

App.svelte

<Modal open={showPopup} onClosed={(data) => onPopupClose(data)}>
	<h2>Testing</h2>
</Modal>

example2.png

Full code

App.svelte

<script>
import Modal from "./Modal.svelte";

	let showPopup = false;

	const onShowPopup = (ev) => {
		showPopup = true;
	}

	const onPopupClose = (data) => {
		showPopup = false;

		console.log(data);
	}

</script>

<main>
	<div class="container">
		<button type="button" class="btn btn-success" on:click={onShowPopup}>Show Popup</button>

		<Modal open={showPopup} onClosed={(data) => onPopupClose(data)}>
			<h2>Testing</h2>
		</Modal>
	</div>
</main>

Modal.svelte

<script>
  import { fade, fly } from "svelte/transition";
  import { quintOut } from "svelte/easing";

  export let open = false;
  export let showBackdrop = true;
  export let onClosed;
  export let title = 'Modal title';

  const modalClose = (data) => {
    open = false;
    if (onClosed) {
      onClosed(data);
    }
  }

</script>

{#if open}
  <div class="modal" id="sampleModal" tabindex="-1" role="dialog" aria-labelledby="sampleModalLabel" aria-hidden={false}>
    <div class="modal-dialog" role="document" in:fly={{ y: -50, duration: 300 }} out:fly={{ y: -50, duration: 300, easing: quintOut }}>
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="sampleModalLabel">{title}</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close" on:click={() => modalClose('close')}>
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">
          <slot></slot>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal" on:click={() => modalClose('close')}>Close</button>
          <button type="button" class="btn btn-primary" on:click={() => modalClose('save')}>Save changes</button>
        </div>
      </div>
    </div>
  </div>
  {#if showBackdrop}
    <div class="modal-backdrop show" transition:fade={{ duration: 150 }} />
  {/if}
{/if}

<style>
  .modal {
    display: block;
  }
</style>

Conclusion

Modal are a nice way to get action from the user which requires immediate attention. Having more than one modal show at the same time is something to be avoided as it might be confusing and annoying for the user.

Let us know your thought about the modal popup component and other feedback in our discord server

Discord - https://discord.gg/AUjrcK6eep