Dialog

Dialog is a TinyMCE UI component used to display simple information.

Contribute to this page

Overview

A dialog is a TinyMCE UI component. Dialogs have their own dialog components which can be used inside dialogs to fulfill a use case. The Dialog API allows showing dialogs (sometimes referred to as modals) in the user application. This API supports the use of dynamic content for all aspects and is easily configurable and overridable.

Use cases

  • Display simple information - The plugin that is used to view the source code is an example of a simple dialog that displays the HTML code from the content.

  • Display complex information - These dialogs can display complex information by using layouts components like tabs or columns to help present information to the user (e.g., help dialog or special characters dialog are tabbed dialogs).

  • Interactive dialogs - These dialogs use web forms to collect interaction data and then apply the data (e.g.: search and replace dialog, uses an input field. Where the input text will be used as the search key. Another example is, special characters or character map dialogs use typeaheads to dynamically narrow down matches as you type)

For example, the search and replace dialog is made up of two input fields - two checkboxes and five buttons. Components are composed by using a configuration structure. The most basic configuration structure is this:

const dialogConfig = {
   title: 'Just a title',
   body: {
     type: 'panel',// The root body type can only be of type Panel or TabPanel
     items: [] //A list of UI component configurations the dialog will have.
   },
   buttons: []    //A list of button configurations the dialog will have.
}

Dialog configuration framework

A Dialog configuration framework has three main parts:

  • Title This is the title of a dialog.

  • Body The body type can take either a Panel or Tab Panel.

  • Footer This section consists of a button or list of buttons.

Body components

Panel

A Panel is a basic container, that holds other components. Many components can be configured inside a panel. In HTML terms, consider a panel a <div> wrapper. A dialog body configuration must begin with either a Panel or a TabPanel.

var panelConfig = {
  type: 'panelbutton',
  items: []
};

Items: - These are an array of component configurations. Any component listed in this page are compatible.

TabPanel

A TabPanel is similar to a panel, where it can hold other components. Tab sections can separate components. Each tab can hold different components which display information for the user that is grouped by tabs. A dialog body configuration must begin with either a Panel or a TabPanel.

var tabPanelConfig = {
  type: 'tabpanel',
  tabs: [
    {
      title: string,
      items: [<other dialog components>]
    },
    ...
  ]
};

Tabs: These are an array of tab configurations. Each tab has a title which is used to reference the tab. The items property in the tab configuration takes a list of components and works the same way as a Panel. A tab can be programmatically be switched by calling dialogApi.showTab(‘title’). For example, the dialog that appears as a result of Help plugin is usually formatted in tab panels.

Help Button

Footer components

Button

The following configuration is used to create a button inside the dialog body :

var buttonConfig = {
  type:  'button'
  name: string,
  text: string,
  disabled: boolean,
  primary: boolean
}

Name: The name property on the button is used to identify which button was clicked. The name property is used as an id attribute to identify dialog components. For example, when the name: foobutton is defined and a user clicks on that button, the dialog onAction() handler will fire and provide event details.name - foobutton. This will allow developers to create a click handler for foobutton. See dialog onAction() configuration.

Text: This will be the text displayed on a button. For example, text: ‘do magic’ will create a button with text do magic. Dialog buttons do not support icons at the moment.

Disabled: (Value: Boolean; Default: False): When set to true, the button will be disabled when the dialog loads. To toggle between disabled and enabled states, use dialogApi.enable(name) or dialogApi.disable(name). See dialog API for more information.

Primary: (Default: False): When set to true, the button will be colored to stand out. The color will depend on the chosen skin.

Button types

The Close button is pre-wired to abort and close the dialog.

The Submit button when clicked will invoke the onSubmit callback provided in the configuration. This callback is used to insert the message.

When onSubmit is called, a dialog instance API is passed in as the parameter. The dialog does not close by default because some use cases may require a server-side callback confirmation.

Cancel button dismisses an action request.

The Custom button can be used to specify a custom operation.

Dialog application examples

Simple dialog

The simple dialogs are used to display simple information, such as the plugins that display the source code or the HTML code from the content in the dialog. These dialogs may not have any values set in the body and footer parts of the dialog configuration.

Source Code

An example of a simple dialog is:

const dialogConfig = {
   title: 'Just a title',
   body: {
     type: 'panel', // The root body type can only be of type Panel or TabPanel.
     items: [] //A list of UI component configurations the dialog will have.
   },
   buttons: [] //A list of button configurations the dialog will have.
}

Using the above example, calling tinymce.activeEditor.windowManager.open(dialogConfig) will create a dialog with the title just a title, an empty body, and an empty footer without buttons.

Complex dialog

The complex dialogs are used to display complex information. These sections can be contained within tabs. For example, the help dialog or the special chars dialog. These dialogs need a way to set the desired content into a defined tab section.

Special Characters

Interactive dialog

The interactive dialogs use web forms to collect interaction data, and then apply the data (e.g.: The search and replace dialog uses an input field. Where the input text will be used as the search key). These are the most complex forms of dialogs and require the users to configure the following:

  • Definition of the desired user input (for example, the search value in the search and replace dialog).

  • Method to process the user input.

  • Required operation to be performed on the user input.

Insert/Edit images

When the dimensions are provided in the above dialog, the interactive dialog can process that information and resize the image to fit the provided values.

This example demonstrates one way of implementing an Interactive Dialog using the redial(config): void method.

Interactive example using redial(config): void

The following example demonstrates custom buttons using the redial dialog for creating two separate dialogs that are cycled through by pressing the Next button.

To see the output of the code, click on the TinyMCE tab on the fiddle below.


var config = {
  title: 'Redial Demo',
  body: {
    type: 'panel',
    items: [{
      type: 'htmlpanel',
      html: '<p>Redial allows the creation of multi-page forms.</p><p>The Next button has been configured to be disabled. When the <b>checkbox</b> is checked, the next button should be enabled</p>'
    }, {
      type: 'checkbox',
      name: 'anyterms',
      label: 'I agree to disagree'
    }, {
      type: 'htmlpanel',
      html: '<p>The next button, calls the redial method which reloads a new dialog in place</p><p>Press next to continue</p>'
    }]
  },
  initialData: {
    anyterms: 'unchecked'
  },
  buttons: [
    {
      type: 'custom',
      name: 'doesnothing',
      text: 'Previous',
      disabled: true
    },
    {
      type: 'custom',
      name: 'uniquename',
      text: 'Next',
      disabled: true
    }
  ],
  onChange: function (dialogApi, changeData) {
    var data = dialogApi.getData();
    /* Example of enabling and disabling a button, based on the checkbox state. */
    var toggle = data.anyterms === 'checked' ? dialogApi.enable : dialogApi.disable;
    toggle('uniquename');
  },
  onAction: function (dialogApi, actionData) {
    if (actionData.name === 'uniquename') {
      dialogApi.redial({
        title: 'Redial Demo - Page 2',
        body: {
          type: 'panel',
          items: [
            {
              type: 'selectbox',
              name: 'choosydata',
              label: 'Choose a pet',
              items: [
                { value: 'meow', text: 'Cat' },
                { value: 'woof', text: 'Dog' },
                { value: 'thunk', text: 'Rock' }
              ]
            },
            {
              type: 'htmlpanel',
              html: '<p>Click done, your pet choice will be printed in the console.log and the dialog should close</p>'
            }
          ]
        },
        buttons: [
          {
            type: 'custom',
            name: 'lastpage',
            text: 'Done',
            disabled: false
          }
        ],
        initialData: {
          choosydata: ''
        },
        onAction: function (dialogApi, actionData) {
          var data = dialogApi.getData();

          var result = 'you chose wisely: ' + data.choosydata;
          console.log(result);
          tinymce.activeEditor.execCommand('mceInsertContent', false, '<p>' + result + '</p>');

          dialogApi.close();
        }
      });
    } else if (actionData.name === 'doesnothing') {
      /* this case should never be met as the button is never enabled. */
    }
  }
};

tinymce.init({
  selector: 'textarea.wizard',
  toolbar: 'wizardExample',
  height: '900px',
  setup: function (editor) {
    editor.ui.registry.addButton('wizardExample', {
      icon: 'code-sample',
      onAction: function () {
        editor.windowManager.open(config)
      }
    })
  }
});


In this redial example, there are two separate dialogs that are cycled through by pressing the Next button. In the configuration structure, the first level is like any other dialog.

The difference is the onAction call loads a new configuration for the dialog using redial. The configuration that is used in the redial(dialogConf) call can be any supported dialog structure. It could even replace this Redial Demo configuration, in the Pet Name Machine example in the compostion section.

This demo also includes the use of dialogApi.enable and dialogApi.disable to disable the Next button when user input is required. For Checkboxes, the onChange callback is used to handle the changes for the checkbox data. The checkbox data is mapped to its defined name: anyterms. When a user clicks or presses Enter on the checkbox, the new value of the checkbox is returned by the getData() call stored in the anyterms property. Given the state of the checkbox, the Next button is either disabled or enabled.

The onAction callback at the root level, is the handler for the Previous and Next buttons. The onAction handler is shared across multiple buttons, and this name property is used to identify the clicked button. The previous button named doesnothing is used to highlight branching.

A Switch statement could be used to handle many buttons.

The onAction callback inside the redial() call, is a separate handler for the redialed dialog. Since there is only one button, which named button triggered the click is not checked. This handler demonstrates the dialogApi.close() API.

Note: Please see this page for a comprehensive list of components.

Dialog composition

The following example demonstrates how data flows through the dialog and how buttons are configured. This is an interactive dialog that inserts the name of a cat into the editor content on Submit. This example is referred to throughout the new dialog instance API section.

The following Pet Name Machine example illustrates an interactive dialog:


/* example dialog that inserts the name of a Pet into the editor content */
var dialogConfig =  {
  title: 'Pet Name Machine',
  body: {
    type: 'panel',
    items: [
      {
        type: 'input',
        name: 'catdata',
        label: 'enter the name of a cat'
      },
      {
        type: 'checkbox',
        name: 'isdog',
        label: 'tick if cat is actually a dog'
      }
    ]
  },
  buttons: [
    {
      type: 'cancel',
      name: 'closeButton',
      text: 'Cancel'
    },
    {
      type: 'submit',
      name: 'submitButton',
      text: 'Do Cat Thing',
      primary: true,
    }
  ],
  initialData: {
    catdata: 'initial Cat',
    isdog: 'unchecked'
  },
  onSubmit: function (api) {
    var data = api.getData();
    var pet = data.isdog === 'checked' ? 'dog' : 'cat';

    tinymce.activeEditor.execCommand('mceInsertContent', false, '<p>My ' + pet +'\'s name is: <strong>' + data.catdata + '</strong></p>');
    api.close();
  }
};

tinymce.init({
  selector: 'textarea.petMachine',
  toolbar: 'dialog-example-btn',
  setup: function (editor) {
    editor.ui.registry.addButton('dialog-example-btn', {
      icon: 'code-sample',
      onAction: function () {
        editor.windowManager.open(dialogConfig)
      }
    })
  }
});



The key highlight in this example is the input field for ‘enter the name of a cat’. The name property catdata is associated with the initalData.

Note: All body components that require a name property also require an initialData property. This is how the relationship between the underlaying data model and the component is declared.

When the dialog is loaded first, the input field is pre-populated with the initial cat.

When initialData.catdata = '' then on load, the input field should be empty.

In this example, two buttons are declared to be placed in the dialog footer, Close and Submit. These are pre-constructed buttons that perform common actions, such as, closing a dialog or submitting a dialog.

Dialog instance API

When a dialog is created, a dialog instance API is returned. For example, const instanceApi = editor.windowManager.open(config);

The instance API is a javascript object containing methods attached to the dialog instance. When the dialog is closed, the instance API is destroyed.

Instance API methods

Methods Description
getData(): <T> getData() returns a key-value object matching the structure of the initialData. See initialData configuration. The object keys in the returned data object represent a component name. For the Insert Cat Name example, data.catdata is the value currently being held by the input field with the name catdata
setData(newConfig: object): void setData(newData) updates the data set. This method also works with partial data sets.
disable(name: string): void Calling disable() and passing the component name will disable the component. Calling enable(name) will re-enable the component.
enable(name: string): void Calling enable() and passing the component name will enable a component, and users can interact with the component.
focus(name: string): void Calling focus() and passing the component name will set the browser focus to the component.
block(message: string): void Calling block() and passing a message string will disable the entire dialog window and display the message notifying users why the dialog is blocked, this is useful for asynchronous data. When the data is ready we use unblock() to unlock the dialog
unblock(): void Calling unblock() will unlock the dialog instance restoring functionality
showtab(name: string): void This method only applies to tab dialogs only. Calling showtab() and passing the name of a tab will make the dialog switch to the named tag.
close(): void Calling the close() method will close the dialog. When closing the dialog, all DOM elements and dialog data are destroyed. When open(config) is called again, all DOM elements and data are recreated from the config.
redial(config): void Calling redial() and passing a dialog configuration, will destroy the current dialog and create a new dialog. Redial is used to create a multipage form, where the next button loads a new form page.

Can't find what you're looking for? Let us know.

Except as otherwise noted, the content of this page is licensed under the Creative Commons BY-NC-SA 3.0 License, and code samples are licensed under the Apache 2.0 License.