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
- Update
::toString() to handle new attributes.
- Create tests
- Create change record
User interface changes
None
API changes
New method
Data model changes
None
Comments
Comment #2
tedbowHere is the first try.
Added
\Drupal\Core\Link::$attributeswith 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::setAttributesyou will overwrite your the attributes for the dialog.So maybe it would make more sense in
::openInDailog()to simply save parameters and then intoRenderable()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(),Comment #3
tedbowI 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.Comment #4
tedbowAdding #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
Comment #5
tedbowComment #6
tedbowI 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
Comment #7
borisson_Should this be an assert instead of an exception?
Comment #8
tedbow@borisson_ yes that makes sense. Fixed
Comment #9
tedbowComment #10
tedbowSimplified 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$optionsparameter supportsSo we can just use this. This allows the same logic to be used from
::toRenderable()and::toString().Right now
::toString()doesn't handle attaching thecore/drupal.dialog.ajax. Not sure how to handle that yet.Comment #11
tedbowAfter looking at this again I wonder why not move all this logic to
\Drupal\Core\Urlitself?This would make dialog links much easier even when using it like this
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
Linkthen 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 fordata-dialog-typeas was suggested by @bedir in #2933379: Automatically add 'use-ajax' class when 'data-dialog-type' is used.(which would no longer be needed.Comment #13
tedbowHere I decided call
setDialogAttributes()to make sure thatgetOption('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.
Comment #14
borisson_Providing this functionality from the url object as well really makes sense, good idea!
Comment #15
benjy commentedI 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'Comment #16
sam152 commentedFor 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.Comment #17
mingsongThanks 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).
Comment #18
sam152 commentedHere is a quick prototype of #16 based on the patches written by @tedbow. The interdiff was quite confusing, so uploading against 8.6 directly.
Comment #19
benjy commentedThis is great, maybe we could update a few uses in core to use the new API?
Comment #21
tedbow@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
openInRendererwould 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.
Comment #22
sam152 commentedI 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 = []),$rendererhere 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 indialog_renderer_testwhich 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 ofopenInRenderer('drupal_dialog.off_canvas')oropenInDialog('dialog', 'off_canvas'). If we're already hard-coding renderer specific code intoLink, why not go all out and name the methods::openInOffCanvas,::openInModaletc? I think the reason is that it would be knocked back from a framework management perspective.Thoughts?
Comment #37
prashant.cHave just created an MR from the patch submitted in #18. Going to test these methods locally first.
Comment #38
prashant.cTo quickly test, return the following code in a block or controller:
Open link in a "modal" window:
Open link in an "off-canvas" window:
Open link in an "off_canvas_top" window:
Comment #39
prashant.cA review of the current code is needed before moving forward with this. After that, we can go ahead with writing tests for this.
Comment #40
smustgrave commented