Problem/Motivation

To make a dialog link in Drupal you have know to do this

'normal_modal' => [
        '#title' => 'Normal Modal!',
        '#type' => 'link',
        '#url' => Url::fromRoute('dialog_renderer_test.modal_content'),
        '#attributes' => [
          'class' => ['use-ajax'],
          'data-dialog-type' => 'modal',
        ],
        '#attached' => [
          'library' => [
            'core/drupal.ajax',
          ],
        ],
      ],

To use the off-canvas dialog:

'off_canvas_link_1' => [
        '#title' => 'Click Me 1!',
        '#type' => 'link',
        '#url' => Url::fromRoute('off_canvas_test.thing1'),
        '#attributes' => [
          'class' => ['use-ajax'],
          'data-dialog-type' => 'dialog',
          'data-dialog-renderer' => 'off_canvas',
        ],
      ],

You have to know about data-dialog-type, use-ajax, data-dialog-renderer.

But now we have Link objects and \Drupal\Core\Link::toRenderable
So making regular are easier but we can't make dialog link this way.

Proposed resolution

Make it really easy and obvious to make dialog links.

Add \Drupal\Core\Link::openInDailog()

Remaining tasks

  1. Update ::toString() to handle new attributes.
  2. Create tests
  3. Create change record

User interface changes

None

API changes

New method

Data model changes

None

Members fund testing for the Drupal project. Drupal Association Learn more

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Status: Active » Needs review
Issue tags: +DX (Developer Experience), +Needs tests
FileSize
2.81 KB

Here is the first try.

Added \Drupal\Core\Link::$attributes with getter/setter because we need attributes for dialog links.

One thing I noticed with this patch is that if you call \Drupal\Core\Link::openInDailog() and then call \Drupal\Core\Link::setAttributes you will overwrite your the attributes for the dialog.

So maybe it would make more sense in ::openInDailog() to simply save parameters and then in toRenderable()
add what is needed to make the dialog link.

This will need test but for now this is how you would create a dialog link:
Link::createFromRoute('Click me!', 'tester.route')->openInDailog('dialog', 'off_canvas')->toRenderable(),

tedbow’s picture

I switched getAttributes() to merge dialogAttributes and regular attributes. So this will return the actual attributes that will be used on the link.

This is important because setAttributes() might be called to set the 'class' but you 'use-ajax' would need to used regardless.

Also added a @todo noting that toString() because this is used for converting in twig templates I think. So it would need to take into consideration attributes now and #attached.

tedbow’s picture

Related issues: +#2529560: Expand support for link objects

Adding #2529560: Expand support for link objects for history

I think adding redirect via 'destination' is important for dialog forms. So I also created #2944791: Create a "setDestination" method on \Drupal\Core\Url to make it easier to set the destination

tedbow’s picture

tedbow’s picture

I previously created #2933379: Automatically add 'use-ajax' class when 'data-dialog-type' is used which also was trying to address some of the complexity of creating dialog links.

Not sure if we would still need that

borisson_’s picture

+++ b/core/lib/Drupal/Core/Link.php
@@ -140,11 +184,56 @@ public function toString() {
+    if (!in_array($type, ['dialog', 'modal'])) {
+      throw new \UnexpectedValueException("The dialog type must be either 'dialog' or 'modal'");
+    }

Should this be an assert instead of an exception?

tedbow’s picture

@borisson_ yes that makes sense. Fixed

tedbow’s picture

Issue summary: View changes
tedbow’s picture

Simplified the patch a bit.

I originally add the '#attributes' => .. to \Drupal\Core\Link::toRenderable() because I thought link objects didn't allow you set attributes on the <a> tag.

Of course this isn't true. The URL object itself takes care of this. Looking at the doc for \Drupal\Core\Url::fromUri() the $options parameter supports

* - 'attributes': An associative array of HTML attributes that will be
* added to the anchor tag if you use the \Drupal\Core\Link class to make
* the link.

So we can just use this. This allows the same logic to be used from ::toRenderable() and ::toString().

Right now ::toString() doesn't handle attaching the core/drupal.dialog.ajax. Not sure how to handle that yet.

tedbow’s picture

After looking at this again I wonder why not move all this logic to \Drupal\Core\Url itself?

This would make dialog links much easier even when using it like this

'dialog_link' => [
   '#title' => 'Click Me Dialog!',
   '#type' => 'link',
   '#url' => Url::fromRoute('tester.dialog_callback')->openInDialog('dialog'),
],

In this case then you won't have to worry about the attributes or adding the 'use-ajax' class.

Then if we just add a helper function to Link then can still do:
'modal_link' => Link::createFromRoute('From object', 'tester.simple_form')->openInDialog();

Regarding the problem making sure 'core/drupal.dialog.ajax' library is always attached I have moved this to \Drupal\Core\Render\Element\Link::preRenderLink() and it simply checks for data-dialog-type as was suggested by @bedir in #2933379: Automatically add 'use-ajax' class when 'data-dialog-type' is used.(which would no longer be needed.

Status: Needs review » Needs work

The last submitted patch, 11: 2944554-11.patch, failed testing. View results

tedbow’s picture

Title: Allow easily creating dialog links from Link objects » Allow easily creating dialog links
+++ b/core/lib/Drupal/Core/Url.php
@@ -655,6 +662,7 @@ public function getOption($name) {
   public function setOptions($options) {
     $this->options = $options;
+    $this->setDialogAttributes();
     return $this;
   }

@@ -672,6 +680,9 @@ public function setOptions($options) {
   public function setOption($name, $value) {
     $this->options[$name] = $value;
+    if ($name == 'attributes') {
+      $this->setDialogAttributes();
+    }

Here I decided call setDialogAttributes() to make sure that getOption('attributes') would always return the correct attributes that would be what is actually is used to generate a link.

Otherwise existing code that might be checking to see if 'data-dialog-type' see if the link will be in dialog can still rely on this.

I am not sure how else right now you would test if a Url object would produce a dialog link.