Bootbox is now deprecated

As of this commit:

the bootbox library for dialogs is now deprecated.

Developers, please use the dialog service in core instead of bootbox in your custom themes and plugins. The bootbox library will be removed from core Discourse entirely sometime in 2023.

Here are some example PRs from plugins and themes that show how to transition to the dialog service:

3 Likes

In my custom theme I have something like this:

<script type="text/discourse-plugin" version="0.8">
    bootbox.alert('alert text');
</script>

How can I access the dialog service in the theme, since it seems to not be globally available?

The easiest option would be to load the service using the lookup. Here’s an example code (this is untested, but we use something like this in core, outside of Ember components/controllers/routes):

<script type="text/discourse-plugin" version="0.8">
  import { getOwner } from "discourse-common/lib/get-owner";
  const dialog = getOwner(this).lookup("service:dialog");
  dialog.alert('alert text');
</script>
1 Like

Thanks. I tried this but am getting an error:
SyntaxError: /discourse/theme-11/initializers/theme-field-36-common-html-script-2: 'import' and 'export' may only appear at the top level. (13:4)

1 Like

Ah, right, we need to use requires in these script tags. If it’s ok to share, what’s the full code of what you are doing here? I’d like to propose an alternative to this <script> tag, we’ve moved most theme code to standalone JS files in the theme components that we maintain ourselves and it might be easier to do a more general refactor here for you.

1 Like

Thanks for the offer! Here is the full script (with some text slightly modified), which was working fine until we noticed the Bootbox popup no longer rendered HTML tags correctly.

<script type="text/discourse-plugin" version="0.8">
    if (typeof bootbox === 'undefined') {
        console.log('Cannot trigger undefined "bootbox"');

        return;
    }

    let currentUser = api.getCurrentUser();

    if (currentUser) {
        api.container.lookup('store:main').find('user', currentUser.username).then((user) => {
            let userGroups = user.groups.map(group => group.name);

            let bodyClasses = userGroups.map(group => `user-group--${group}`);

            document.querySelector('body').classList.add(...bodyClasses)

            let showPopup;

            switch(true) {
                case userGroups.includes('restricted_member'):
                    showPopup = true;
                    break;
                case userGroups.includes('users_all'):
                case userGroups.includes('officers'):
                    showPopup = false;
                    break;
                default:
                    showPopup = true;
                    break;
            }

            if (!showPopup) {
                return;
            }

            let alertHeading = '<h2>Oh no!</h2>';

            let alertBody = '<p>Your account is currently restricted and you no longer have access to valuable resources. <a href="https://meta.discourse.org">Click here</a> for more information.</p>';

            bootbox.alert(alertHeading + alertBody);
        });
    }
</script>
1 Like

Hi Tim,
Here’s the alternative, note that the contents here needs to go in an initializer JS file in a component. You can see a simple example of a full component structure here.

import { withPluginApi } from "discourse/lib/plugin-api";
import { getOwner } from "discourse-common/lib/get-owner";
import { schedule } from "@ember/runloop";
import { htmlSafe } from "@ember/template";

export default {
  name: "tester-initializer",

  initialize() {
    withPluginApi("0.8", (api) => {
      const currentUser = api.getCurrentUser();

      if (currentUser) {
        const userGroups = currentUser.groups.map((group) => group.name);
        let showPopup = false;

        switch (true) {
          case userGroups.includes("admins"):
            showPopup = true;
            break;
        }

        if (!showPopup) {
          return;
        }

        const alertHeading = "Oh no!";
        const alertBody =
          'Your account is currently restricted and you no longer have access to valuable resources. <a href="https://meta.discourse.org">Click here</a> for more information.';

        schedule("afterRender", () => {
          // delay needed so that the dialog service is loaded
          const dialog = getOwner(this).lookup("service:dialog");
          dialog.alert({
            message: htmlSafe(alertBody),
            title: alertHeading,
          });
        });
      }
    });
  },
};

Hope this helps.

3 Likes

This is perfect. Thanks so much!

1 Like