Problem/Motivation
The AddCssCommand (typically used by AjaxResponseAttachmentsProcessor) always adds the missing-but-required CSS assets to the beginning of the HTML <head>
.
Theme developers often rely on the loading order of the CSS assets, but due to the functionality described above, this becomes impossible when an ajax-driven operation happens on the page.
Proposed resolution
After superficially checking the CssCollectionRenderer, I would say it works fine: It returns the required CSS assets in the expected order (respecting the library dependencies).
Since we know that we have all off the required dependencies in the 'before-ajax' state, and the newly added CSS assets seems to be in the expected order, my proposed solutions are:
- Modify
ajax.js
's add_css command to add the CSS assets after the last stylesheet referrer link on the page<link rel="stylesheet">
. - Append to the HTML
<head>
(or<body>
?).
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Original report
See ajax_render()
And more specifically these lines:
if (!empty($styles)) {
$extra_commands[] = ajax_command_prepend('head', $styles);
}
When you try to add stylesheets through ajax, isn't the expected behaviour that these stylesheets actually do something?
By prepending them to the head, you basically give them the lowest possible priority towards all other already loaded stylesheets.
Suggestion: use ajax_command_append() instead.
Example (shoddy, but it illustrates the problem):
- I want to add and trigger a jQuery dialog through AJAX
- I load the files using drupal_add_library and drupal_add_css in my AJAX callback
- The JavaScript behaves as expected, the CSS doesn't because it has to yield to the system stylesheet for dialogs
Comment | File | Size | Author |
---|---|---|---|
#41 | 1461322-10-1-x-41.patch | 472 bytes | joegraduate |
| |||
#40 | 1461322-10-0-x-40.patch | 342 bytes | joegraduate |
| |||
#29 | 1461322-29.patch | 998 bytes | KapilV |
#25 | 1461322-25.patch | 998 bytes | joegraduate |
#23 | 1461322-23.patch | 990 bytes | joegraduate |
Comments
Comment #1
nod_That's an issue indeed.
Comment #2
JeremyFrench CreditAttribution: JeremyFrench commentedThis is interesting.
My initial thought is that you would add CSS to style new content which you are adding via ajax which may not be in the original request. As these would (hopefully) have different classes/selectors to the content in the page it doesn't matter if you are prepending or appending to head. I believe that order precedence in CSS is only important if all other precedence are equal.
So your example (with jquery ui) should still work prepending or appending. Unless you are using IE then you will be caught by #1071818: Lazy-loading CSS fails in IE
The questionable advantage of appending is that you would be able to change existing styles.
One solution would be to provide an option which defaults to prepend, but allows you to append if you really want to.
Comment #3
effulgentsia CreditAttribution: effulgentsia commentedDoing append() would mean module CSS overriding theme CSS, right? Is it possible to merge into the correct place? We already have <style> and <link> tags organized by library vs module vs theme, but I don't know how we can expose that information to the tags themselves for JS to insert each one in the right place.
Comment #4
JeremyFrench CreditAttribution: JeremyFrench commentedIn theory it may be possible to change the text of the CSS, or the CSS in the DOM, but looking at this compatibility table I think we would be walking into a cross browser nightmare.
Comment #5
nod_another good use-case for
data-
maybe?Comment #6
kristiaanvandeneynde@JeremyFrench I agree this needs to be validated so it doesn't break other use cases.
As suggested in #2 and #3: having the option to choose where you want the style to be placed would be an even better solution than my suggestion in the original post.
But keep in mind that if you are adding CSS through an event-triggered AJAX call (so not lazy loading after page load), it is very likely that you want that CSS to do something.
In the dialog example you have two possible workflows:
Comment #7
JeremyFrench CreditAttribution: JeremyFrench commented@kristiaanvandeneynde
This is why you would namespace your attributes/styles. That way it doesn't matter if they are prepended or appended.
If you don't do this and you append styles you will end up with CSS clashes and changing the styles on the page.
edit adding link to Specifity guide, remember position only affects what rules apply if specificity is equal, and then rules are combined rather than replaced.
In short if your module/library needs position to have it's styles apply it is probably not using CSS correctly. The only case when this is not true is if you are intentionally trying to change the styles already on a page.
Comment #8
kristiaanvandeneyndeI realize forcing a high specificity (or, as you call it, namespacing your css) is a possible solution, but is it not the be all end all solution.
Suppose a very naughty themer added !important to one of his stylesheets. No matter how much I would namespace my CSS or how much I would add !important myself; if my stylesheet were loaded before his, I'd still be screwed out of theming my newly added HTML.
Search for "specificity wars" for more information on that subject.
Source: http://www.stubbornella.org/content/2010/07/01/top-5-mistakes-of-massive-css/
Comment #9
kristiaanvandeneyndeSetting to minor since this would only cause problems in <1% of use cases and an easy workaround is namespacing.
Doesn't mean this shouldn't be further examined, though.
Comment #10
ezman CreditAttribution: ezman commentedAfter a lot of time spent trying various methods, I've come up with the following workaround which may help others.
I have a custom module that provides AJAXified editing capabilities using Dialog API.
My problems were twofold:
1) jQuery UI's CSS was being loaded into the Dialog after my theme's CSS, and I had customised the look of the Dialog in my theme's CSS.
2) Media's media.js kept loading in after my theme's script.js, and I had overriden Drupal.behaviors.mediaElement in my theme's Javascript.
I tried various methods of altering the AJAX commands, and also tried setting $form['#attached']['js] and ['css'] properties, but nothing worked.
Here's what's worked for me. It's not pretty, but it seems to do the trick. Even after managing to get my CSS output at the end, I found that the media.js file was still being loaded after my theme's, so used the jQuery update hook_js_alter() method to swap Media's media.js for my own version.
You'll notice I've added the JS and CSS twice, and I don't know which of the two methods is the one that works, or if both are necessary (It's very late, been working the whole weekend, finally got it working now, and wanted to note this down before I forget it and move on to the next major problem tomorrow morning. will probably have to revisit this and tidy it up a little bit later on)
But I hope this helps someone and stops them spending as long as I have done trying to get it working!
Comment #11
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedI don't think this is an edge case - the CSS system allows 'group' and 'weight' to be specified on CSS files for a reason, and it's bad that they are sometimes ignored. Giving your CSS rules a higher specificity can help, but it's a workaround and not always a good option (for example, if the two files that are competing with each other come from code you don't control, e.g. an outside library, a contrib module, etc).
In the meantime here's the workaround I wound up using for this, if it helps anyone (this is Drupal 7 code):
(where MYLIBRARY is a library defined in MYMODULE's hook_library() that has the CSS files which I want to force to be appended)
This is simpler code than the example in the previous comment, but also more brute-force (it will force all CSS files added via that Ajax request to be appended, not just my own). If you care about the difference, then the earlier code example may work better for you.
Comment #18
huzookaComment #19
huzookaComment #20
seanBJust ran into this when we enabling bigpipe for an existing site (found this while investigating #2886657: BigPipe changes CSS evaluation order). I personally think appending is a better default than prepending. Here is a simple patch to change this for anyone who needs it.
This is just a quickfix though. I agree the "real" solution would be to insert all CSS as you should expect based on the styling level, weight and dependencies.
Comment #21
Wim Leers@seanB Any reason to not mark this
and have a test run?Comment #22
seanBNot really, let's see.
Comment #23
joegraduateThe attached patch is a re-roll of #20 that should apply cleanly to 8.9.x.
#20 applies cleanly to 8.8.x but not 8.9.x.
Comment #25
joegraduateThe attached patch is a 9.1.x re-roll of #23.
Comment #26
joegraduateComment #28
joegraduateComment #29
KapilV CreditAttribution: KapilV as a volunteer and at Innoraft for Drupal Care, Drupal Association commentedHere a patch for 9.2.x.
Comment #32
seanBJust found something funky because of this patch.
/assets/vendor/jquery.ui/themes/base/core.css
is added via multiple CSS libraries in core (core/drupal.dialog
,core/drupal.autocomplete
,core/jquery.ui
).drupalSettings.ajaxPageState
. This only compares libraries, not actual CSS/JS files./assets/vendor/jquery.ui/themes/base/core.css
being added a second time, and having.ui-front { z-index: 100; }
override.ui-dialog { z-index: 1260; }
from/themes/seven/css/components/dialog.css
, since the new CSS files are now appended instead of prepended.Fixed it with some custom CSS in our theme for now. Since jQuery UI is being removed it is probably not worth adding a new issue to make sure
core/drupal.dialog
,core/drupal.autocomplete
,core/jquery.ui
don't add the same file, even though that is the real cause.Just spent some time figuring this out, so thought it would be good to share :).
Comment #33
huzooka@seanB, I think that #32 should be addressed as well.
And we definitely need test coverage.
Comment #34
nickvanboven CreditAttribution: nickvanboven as a volunteer commented#32 also seeing this when using the gin theme and this patch, css of media library is added after gin css and breaks the media library display.
Comment #38
trackleft2 CreditAttribution: trackleft2 as a volunteer and at The University of Arizona commentedMy theory is that we have at least three ways of interacting with libraries.
Overrides
Dependencies
Extensions
This patch is great for overrides and dependencies, but extensions are a problem. See https://www.drupal.org/node/2497313
Overrides need to replace a library
Dependencies need to prepend a library
Extensions need to be appended to a library
Comment #40
joegraduateRe-rolled patch from #25 for 10.0.x.
Comment #41
joegraduateRe-rolled patch from #40 for 10.1.x.
Comment #42
acbramley CreditAttribution: acbramley at PreviousNext for Service NSW commentedBit confused by this one, I've spent the day debugging some odd CSS issues with paragraphs and entity embed modals.
I've tracked it down to
/core/themes/claro/css/components/jquery.ui/theme.css
being added multiple times on the page when adding a new paragraph (added via the add_css command) but these CSS files already are appearing after the existing CSS files. This leads to some UI styling bugs like the one described here.EDIT: Ahhh,
prepend
is only used if response.data is a string, which is now deprecated. So it's usingloadjs
instead.