Skip to content

Latest commit

 

History

History
312 lines (245 loc) · 6.26 KB

README.md

File metadata and controls

312 lines (245 loc) · 6.26 KB

Intro

This is a repository that shows how to create a global modal which is equivalent to browser window.confirm with Alpinejs.

You can call window.customConfirm to get boolean true or false like below.

  isConfirmed = await window.customConfirm({...});
  console.log(isConfirmed) // 👈 true or false

Step 0: Setup project

Please create Static HTML project at https://stackblitz.com/

We, only use two files

  • index.html
  • script.js

In index.html, add Alpine.js cdn

<html>
  <head>
    <meta charset="UTF-8" />
    <!-- Custom script -->
    <script src="script.js"></script>

    <!-- 👇 Alpine.js from cdn -->
    <script
      defer
      src="https://unpkg.com/[email protected]/dist/cdn.min.js"
    ></script>
  </head>
  <body>
    ...
  </body>
</html>

Step 1: Define window.customConfirm and assign it to @click on button

In script.js

window.customConfirm = () => {
  console.log('customConfirm called');
};

In index.html

<button
  x-data
  @click="
    window.customConfirm();
  "
>
  open dialog
</button>

Now, we defined window.customConfirm function and can call it from button by using Alpinejs.

Step 2: Add Modal

In this step, we're going to add modal.

Please modify index.html like below.

index.html

<body>
   <!-- Mimic nested div -->
    <div>
      <div>
        <button x-data @click="window.customConfirm();">open dialog</button>
      </div>
    </div>

    <!-- Modal -->
    <div style="background: pink; border: solid; padding: 40px">
      <h1>Modal Title</h1>
      <p>Modal message</p>
      <button>Cancel</button>
      <button>OK</button>
    </div>
</body>

Step 3: Add Alpine Store

To handle modal's open state, let's add Alpine store.

script.js

document.addEventListener('alpine:init', () => {
  Alpine.store('confirmModal', {
    open: false,
    toggle() {
      this.open = !this.open;
    },
  });
});

Then in index.html

<body>
  ...
  <!-- Modal -->
  <div x-data x-show="$store.confirmModal.open"> <!-- 👈 Add this line -->
    <div style="background: pink; border: solid; padding: 40px">
      <h1>Modal Title</h1>
      <p>Modal message</p>
      <button>Cancel</button>
      <button>OK</button>
    </div>
  </div>
</body>

And then, script.js

window.customConfirm = () => {
  Alpine.store('confirmModal').toggle();
};

Now we can toggle modal like below.

Step 4: Add function onOk and onCancel

In this step, we're going to add function when modal OK and Cancel is clicked.

script.js

🚫 Note: this code is for showing wrong implementation.

document.addEventListener('alpine:init', () => {
  Alpine.store('confirmModal', {
    open: false,
    toggle() {
      this.open = !this.open;
    },
    onOk() {
      console.log('onOk clicked');
      return true;
    },
    onCancel() {
      console.log('onCancel clicked');
      return false;
    },
  });
});

Then in index.html

...
  <!-- Mimic nested div -->
  <div>
    <div>
      <button
        x-data
        @click="
          isConfirmed = window.customConfirm();
          console.log(isConfirmed);
        "
      >
        open dialog
      </button>
    </div>
  </div>

  <!-- Modal -->
  <div x-data x-show="$store.confirmModal.open">
    <div style="background: pink; border: solid; padding: 40px">
      <h1>Modal Title</h1>
      <p>Modal message</p>
      <button
        @click="
          $store.confirmModal.onCancel();
        "
      >
        Cancel
      </button>
      <button
        @click="
          $store.confirmModal.onOk();
        "
      >
        OK
      </button>
    </div>
  </div>
...

Now, we can assign onOk and onCancel function to buttons in modal.

🚫 But, it does not return boolean true or false value.

In the next step, we will learn how to fix this issue.

Step 5: Promise

To get boolean value from window.customConfirm, we need to use Promise.

For example, let's re write window.customConfirm

script.js

window.customConfirm = () => {
  return new Promise((resolve, reject) => {
    resolve(true);
    // resolve(123) 👈 try returning various values by using `resolve`
    // resolve("hoge")
  });
  // Alpine.store('confirmModal').toggle();
};

And in index.html

<button
  x-data
  @click="
    isConfirmed = await window.customConfirm();
    console.log(isConfirmed);
  "
>
  open dialog
</button>

Alpine @click does not need to declare async, just using await is enough.

You will see now, window.customConfirm returns boolean true value in console.

Step 6: Assign resolve(true) to onOk and onCancel

First we make store onOk and onCancel blank function at begining.

document.addEventListener('alpine:init', () => {
  Alpine.store('confirmModal', {
    open: false,
    toggle() {
      this.open = !this.open;
    },
    onOk() {},
    onCancel() {},
  });
});

Then modify window.customConfirm like below.

window.customConfirm = () => {
  return new Promise((resolve, reject) => {
    const confirmModal = Alpine.store('confirmModal');

    // Open Modal
    confirmModal.open = true;

    // Assign logic: when OK button is clicked, close modal and return true
    confirmModal.onOk = () => {
      confirmModal.open = false;
      resolve(true);
    };

    // Assign logic: when Cancel button is clicked, close modal and return false
    confirmModal.onCancel = () => {
      confirmModal.open = false;
      resolve(false);
    };
  });
};

By doing this, we override onOk and onCancel logic when window.customConfirm is called to return boolean true or false.

Finish!

This is the final code of this tutorial.

https://web-platform-rrgo1m.stackblitz.io

Realworld example with Tailwind

And this is a confirm modal with tailwind which is shown in Intro section. https://stackblitz.com/edit/web-platform-nwc4sm

Refs

👋 This example is totaly inspired by this great stackoverflow answer https://stackoverflow.com/a/58891905/6037441