Skip to main content

Understanding Drupal 8's Modal API and Dialog Controller

It is common knowledge that Drupal 8 contains Views module, thanks to the work from the Views in Drupal Core (VDC) initiative.

Our contribution to the VDC initiative was working on abstracting Views UI's modal pattern into a generic modal API in core.

Recently one of the coolest pieces of this API was committed to core.

You can now load any content in a modal simply by adding a class and an attribute to any link.

Sound cool? Read on to learn more.

by Lee Rowlands /

Improving the A11y of jQuery UI Dialog

First a little background. Views UI in Drupal 7 uses jQuery UI's Dialog component to display modals for configuring views.

Historically, one of the main criticisms of Views UI's use of modals and of jQuery UI Dialogs in general has been their accessibility. So if we were going to commit to a Modal API in core, firstly it needed to be accessible.

As part of the Drupal 8 cycle, we worked with Everett Zufelt (the Drupal 7 Accessibility maintainer) and Scott Gonzalez (of the jQuery UI project) to improve the accessibility of jQuery UI's Dialogs. This culminated in some of our improvements making it into the jQuery 1.10 release (I even made it into Authors.txt for jQuery UI - nice!).

So thanks to the hard work of the jQuery UI team and Everett, with a little help from us - jQuery UI 1.10's Dialog component is now a best-of-breed solution with respect to accessibility in a Rich Internet Application. Open Source multi-project collaboration for the win. Once again this highlights the Proudly Invented Elsewhere approach consistent throughout Drupal 8 Core.

Creating a Modal API - v1

So the first iteration of abstracting the Modal API from Views UI basically moved the code from Views UI module into includes/ajax.inc and ported the 'Delete' link on admin/content/node to use a dialog. This however was a fairly involved process so was simplified down to allow for a #dialog key in an element's #ajax options.

While this made implementing a dialog fairly straight forward, it was too rudimentary to be of use in complex situations, such as in Views UI.

Refining the Modal API (aka v2)

The second iteration of the Modal API re-introduced ajax commands for showing and closing modals and dialogs. This allowed for more complex use cases but unfortunately removed the simple #dialog property from the #ajax options, meaning you needed to return an array of AjaxCommand objects in your controller in order to use the API.

Making the API simple to use

So for the third iteration (which was really just an enhancment on top of the second iteration) we were aiming to make it easy to make a link return a dialog.

Given we had a new routing system in core that was able to return two or more different responses from the same path based on the incoming request, we figured it would be possible to make the Modal API use that and some progressive enhancement to turn an ordinary link into a Modal. This would ensure that

  1. Pages were still visible for users without JavaScript
  2. Those with JavaScript on received the enhanced experience

To do this we implemented a DialogEnhancer (an implementation of Symfony2's RouteEnhancerInterface) (note this has since been consolidated into the enhancer as part of a larger enhancer cleanup) to listen for incoming requests that contained an Accept: application/vnd.drupal-modal or Accept: application/vnd.drupal-dialog header. If such a header exists, the DialogEnhancer takes the incoming request and forwards it to the DialogController. The DialogController then forwards the request to the Controller that would normally serve the request at that path. This is done using a sub-request. Sub-requests are another cool feature of the new routing system that were not possible in Drupal 7 but are possible in Drupal 8 thanks to the WSCCI Initiative with the Symfony Routing system under the hood.

The DialogController then renders the response from the regular controller for that route and wraps it in the required AjaxCommands.

Setting the accept header

So clearly in order to make this fly, you need a means of telling Drupal to instruct the browser to send a particular Accept header.

This is done for you by the ajax JavaScript API, all you have to do is set a class and an attribute on your link as follows

  <a href="/admin/config/development/sync/diff/block.block.bartik.search" class="use-ajax" data-accepts="application/vnd.drupal-modal">Show me a modal</a>

The key elements here are you need to set the class to use ajax so the JavaScript ajax API knows the process the link and then you need to set the data-accepts attribute to one of application/vnd.drupal-modal or application/vnd.drupal-dialog depending on whether or not the dialog should be modal.

Controlling Dialog Options

You can also control attributes of your Dialog such as size by setting a data-dialog-options attribute.

This expects encoded json and accepts any valid options accepted by jQuery UI Dialog - eg

  <a href="/admin/config/development/sync/diff/block.block.bartik.search" class="use-ajax" data-accepts="application/vnd.drupal-modal" data-dialog-options="{"width":500}" >Show me a 500px wide modal</a>

See it in action

What's Next and Limitations?

WSCCI Conversions

We made a deliberate decision to not support legacy routes with the Dialog Controller. A legacy route is a page callback declared using the Drupal 7 style hook_menu(), ie a route that does not use a routing.yml file.

We made this decision because

  1. The WSCCI Meta is moving fast and conversions to routes from hook_menu() is progressing well
  2. The goal is to remove the legacy backwards-compatible support for routes defined in hook_menu()
  3. Getting some cool Dialog awesome-sauce will help motivate folks to convert their page callbacks

So in order for us to get the maximum mileage out of this cool feature, we need help converting legacy routes - plus if you've been wanting to start learning Drupal 8, the WSCCI conversion issues are a great way to kickstart your learning. Drupal 8 includes a raft of new concepts to learn - getting involved now will keep you ahead of the curve.

Head over to the META issue or come to a Core mentoring session if you're keen to get involved.

More core conversions

Now that core has a generic modal API, we need to move Views UI over to use it - see this issue.

Nate Haug (@quicksketch) and Wim Leers are working to move CKEditor's Dialogs to use the Core API in this issue.

Slightly less daunting is converting the remaining confirm forms to use the new dialog api, that issue would also introduce you to FormInterface, if you've not seen it already. Also that issue makes the same functionality work with routes that use the HtmlFormController, meaning confirm forms in modals!

Time permitting I may do some tinkering around in overlay.js to see if we can leverage the modal API instead of an iframe for the Overlay module. I don't expect anything to come of it and its more of a curiousity thing, but given the a11y improvements in jQuery UI Dialog, especially compared to Overlay, I think it is well worth a look. We had an issue for that but its been marked won't fix given the improvements being made to overlay to address a11y concerns.

If any of these sound like something you'd be interested in, head to those issues in the queue to lend a hand.

On Drupal 8's Learning curve

I'll admit that I didn't really get the point of the WSCCI routing conversions until I started working on the DialogEnhancer/DialogController.

Up until that point (other than the REST module), I didn't really see much more functionality in the new routing system compared too the old hook_menu() approach

Once I started working on this patch and saw the enhancer intercept the incoming routes based on Accept header, forward them to the Dialog Controller, which in turn forwarded it to the original controller via a sub-request, and then wrapped the response in AjaxCommands, I was sold. Hook, line and sinker.

The new routing system is without a doubt awesome. If you've been hearing things about Drupal 8 being like learning everything over again from scratch then I think you should consider the significant gains in flexibility we are bringing to fruition because of these major-refactors.

There is talk in the Drupal Community that the Drupal 8 learning curve will discourage a lot of old hats and even perhaps discourage new contributors to the project. My opinion is that the vast number of cool new features in Drupal 8 will mean that clients, business units and business owners will want and perhaps even demand Drupal 8; and developers who've been avoiding it because of the perceived learning curve will have no choice but to up-skill.

Drupal 8 is going to take Drupal to a whole new level and the fact that you're reading this no-doubt means you want to be there at the front of the wave. The time to learn Drupal 8 is now - while it is still being built. As they say - get on the bus, or get run over by it!

Posted by Lee Rowlands
Senior Drupal Developer

Dated

Comments

Comment by Tomasz

Dated

Thanks for a great article! Sounds pretty amazing. I am definitely in the bus :).

Comment by SteffenR

Dated

The dialog API changed since the blog post was created. Maybe you'll update your samples based on my example:
// Build link for dialog.
$link_url = Url::fromRoute('entity.taxonomy_term.add_form', array(
'taxonomy_vocabulary' => 'vocab_name')
);
$link_url->setOptions(array(
'attributes' => array(
'class' => array('use-ajax'),
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode(array(
'width' => 700,
)),
))
);
$link_add_unit_display_name = \Drupal::l('Create unit display name', $link_url);

Regards,
SteffenR

Comment by mark

Dated

how would one set a callback function after the content has been retrieved but before it is rendered in the html?

Comment by Stephen Ollman

Dated

above example didn't work. Below example does:

<pre>
<a href="/admin/config/development/sync/diff/block.block.bartik.search" class="use-ajax" data-accepts="application/vnd.drupal-modal" data-dialog-options="{&quot;width&quot;:500}" >Show me a 500px wide modal</a>
</pre>

Comment by Peter

Dated

Does the D8 modal library provide any sort of client side "hook"? I use a mouseup event to trigger my jq script which does a .click() to trigger one of my modal trigger links (which opens an entity edit form in a modal). This all works great. But, I need to pass a number calculated in my jq script to the form opened in the modal. I think it is possible in javascript to have "hooks" like we think of them in Drupal/PHP, where a user's script could set a global variable which the modal javascript code could read and send back in its modal open request.

Comment by Lee Rowlands

Dated

It uses jquery UI dialog, so all the events it fires exist

Comment by Peter

Dated

ughh.. wrote a big comment, but your recaptcha failed and lost everything i typed..

Comment by iyyappan

Dated

Hi

how can I pass class to modal popup?

Thanks

Comment by vaibhav

Dated

How to prevent dialog box from closing on form validations or errors?

Pagination

Add new comment

Restricted HTML

  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <h2> <h3> <h4> <h5> <h6>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Not sure where to start? Try typing "hello" or "help" if you get stuck.