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 links is easier but we can't make dialog links 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

Issue fork drupal-2944554

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

tedbow created an issue. See original summary.

tedbow’s picture

Status: Active » Needs review
Issue tags: +DX (Developer Experience), +Needs tests
StatusFileSize
new2.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

StatusFileSize
new3.7 KB
new3.57 KB

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

StatusFileSize
new1.24 KB
new3.45 KB

@borisson_ yes that makes sense. Fixed

tedbow’s picture

Issue summary: View changes
tedbow’s picture

StatusFileSize
new2.73 KB
new3.05 KB

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

StatusFileSize
new6.99 KB
new4.41 KB

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.

borisson_’s picture

Issue summary: View changes

Providing this functionality from the url object as well really makes sense, good idea!

benjy’s picture

I don't think adding this directly to the LinkUrl object scales very well, Url is already huge and I don't know why it would want to know about dialogs.

There are likely to be other "features" bound to links in the future. Couldn't this be a new render element if we want to encapsulate those options, then the dev experience would be '#type' => 'link_off_canvas'

sam152’s picture

For an implementation of the current DX, we could introduce DecoratedLinkMainContentRendererInterface, then move the logic for adding stuff to the link object to renderers themselves. Then each renderer could decide what additional metadata a link needs to correctly satisfy the requirements of the renderer, set sane defaults etc.

mingsong’s picture

Thanks for the great feature.

Just raise an issue with Bootstrap theme.

Apparently, Bootstrap theme has its own dialog.ajax.js file that is the same name as core/drupal.ajax (/core/misc/dialog/dialog.ajax.js).

I am not sure if it is a problem, but I couldn't get the feature working with Bootstrap theme (https://www.drupal.org/project/bootstrap).

sam152’s picture

Status: Needs work » Needs review
StatusFileSize
new6.76 KB

Here is a quick prototype of #16 based on the patches written by @tedbow. The interdiff was quite confusing, so uploading against 8.6 directly.

benjy’s picture

+++ b/core/lib/Drupal/Core/Render/MainContent/DialogRenderer.php
@@ -7,12 +7,13 @@
+class DialogRenderer implements LinkableMainContentRendererInterface {

+++ b/core/lib/Drupal/Core/Render/MainContent/ModalRenderer.php
--- a/core/lib/Drupal/Core/Render/MainContent/OffCanvasRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/OffCanvasRenderer.php

This is great, maybe we could update a few uses in core to use the new API?

Status: Needs review » Needs work

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

tedbow’s picture

+++ b/core/lib/Drupal/Core/Link.php
@@ -147,4 +147,16 @@ public function toRenderable() {
+  public function openInRenderer($renderer, array $options = []) {
+    $this->getUrl()->openInRenderer($renderer, $options);
+    return $this;
+  }

@Sam152 the idea of moving the logic to the renderers themselves is interesting and would more flexible for future renderers.

My concern about this approach is it moves the DX problem from having to know what specific attributes to add to render array to having to know what "renderers" are in Drupal. Then also knowing the difference "main content renderers"(though not mentioned here) and other uses of the "renderer" in core.

It maybe that to everyone involved in this issue that the meaning is clear but I would doubt that would the case for Drupal developers who are trying to make a contrib module or especially some simple custom code where they would like to create link that will open in dialog.

If feel like before I got involved in core development(though with a lot of d8 contrib experience) a "main content renderer" would mean nothing to me and if I was creating a link and was searching available methods on the Link object ro figure out how to make it open in dialog, then openInRenderer would not be a method I would explore further.

For that reason I am not sure this solves the initial idea of this issue to "Allow easily creating dialog links". It makes it easier if you already have considerable core knowledge otherwise I don't think it does.

sam152’s picture

I agree, the naming is really tricky. Maybe there is something more friendly that works in this context?

I would however also argue #11 has some confusing terminology. Consider the signature:

public function openInDialog($type = 'modal', $renderer = NULL, array $options = []),

$renderer here is explained in the docblock but also has a different meaning to main content renderers or the renderer service for example. The only reference to the "subtype" of a main content renderer in core I could find was in dialog_renderer_test which calls this a "mode".

I think it makes sense to stick with an approach like in #18 under the hood and delegate the DX aspect of providing more concrete methods to some other part of core. Ideally there would just be a single method like: $link->openInOffCanvas(); instead of openInRenderer('drupal_dialog.off_canvas') or openInDialog('dialog', 'off_canvas'). If we're already hard-coding renderer specific code into Link, why not go all out and name the methods ::openInOffCanvas, ::openInModal etc? I think the reason is that it would be knocked back from a framework management perspective.

Thoughts?

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

kunal.sachdev made their first commit to this issue’s fork.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

prashant.c made their first commit to this issue’s fork.

prashant.c changed the visibility of the branch 2944554-allow-easily-creating to hidden.

prashant.c’s picture

Have just created an MR from the patch submitted in #18. Going to test these methods locally first.

prashant.c’s picture

  1. Type "modal" worked fine.
  2. Fixed issues for type "off_canvas"
  3. Handled for the "off_canvas_top"

To quickly test, return the following code in a block or controller:

Open link in a "modal" window:

    $url = Url::fromRoute('entity.node.canonical', ['node' => 1], ['absolute' => TRUE]);
    $link = Link::fromTextAndUrl($this->t('Read more'), $url);
    $link->openInRenderer('modal');
  return [
      'read_more' => $link->toRenderable(),
  ];

Open link in an "off-canvas" window:

    $link->openInRenderer('off_canvas');

Open link in an "off_canvas_top" window:

    $link->openInRenderer('off_canvas', ['position' => 'top']);
prashant.c’s picture

Status: Needs work » Needs review

A review of the current code is needed before moving forward with this. After that, we can go ahead with writing tests for this.

smustgrave’s picture

Status: Needs review » Needs work

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.