Problem/Motivation
When adding messages in javascript applications such as the Media Library it would be nice to be able to rely on a core ajax command, rather than having to recreate all of this functionality for each application.
See #3059955-22: It is possible to overflow the number of items allowed in Media Library
Proposed resolution
Quoting @phenaproxima:
We should add a new AJAX command, \Drupal\Core\Ajax\AddMessageCommand, which can add messages to a Drupal.Message instance associated with a particular selector. It should also have an option to clear existing messages.
So, the JavaScript side:
// In JavaScript, always do this unconditionally.
$(someElement).prepend('<div class="target-selector"></div>');
Drupal.AjaxCommands.prototype.addMessage = function (response) {
const messages = new Drupal.Message(document.querySelector(response.selector));
messages.add(response.message, response.id, response.type);
}
And on the PHP side:
use Drupal\Core\Ajax\AddMessageCommand;
return (new AjaxResponse())
->addCommand(new AddMessageCommand('.target-selector', 'You chose too many things!', 'limit exceeded', 'error')->clearAll());
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
Comments
Comment #2
oknateThis was implemented by @bjmnm in #3081526-11: Add announcement after clicking 'Save and select' in the media library.
https://www.drupal.org/files/issues/2019-10-01/3081526-11.patch
It just needs the non-media library specific parts removed.
Comment #3
oknateComment #4
oknateComment #5
oknateI make a change in #3059955-34: It is possible to overflow the number of items allowed in Media Library to allow clearing the previous messages. I think we should either have that feature or add a separate command for it.
Comment #6
oknateComment #7
wim leersComment #8
wim leersThis blocks #3081526: Add announcement after clicking 'Save and select' in the media library. and #3059955: It is possible to overflow the number of items allowed in Media Library, both of which are major bugs.
Comment #9
oknateHere's @bnjmnm's patch for #3081526: Add announcement after clicking 'Save and select' in the media library. with the code specific to that issue removed.
Comment #10
droplet commentedWhy not make it more generic?
`Drupal.Message` is Class. You can add another parameter to call the method rather than specific `add` only
e.g.:
Comment #11
oknateHere's a duplicate message bug + fix.
Re #10, that's an interesting idea. I'll explore that later tonight.
Note, I added this change because it would throw an error when calling clear when using the default selector, this is probably a separate bug:
Comment #13
oknateI forgot to commit changes to the test form.
Comment #14
wim leersI think these aspects need to be reviewed & approved by the people who led #77245: Provide a common API for displaying JavaScript messages.
EDIT: Pinged @nod_ and @tedbow.
Comment #15
droplet commentedComment #16
tedbowI think we are suppose to use
@var boolinsteadCould we remove
data-drupal-messagesand put a @see or mention of the JS methodDrupal.Message.defaultWrapper(). Because it is little more complicated.These are unrelated changes. They removed if I run
yarn prettier. So this should be run on this patch. https://www.drupal.org/node/2986680response.messageWrapperQuerySelectorneeds to be documented in the doc block.I think this is fixed in #3052526: Drupal.Message.clear throws exception if messageWrapper not initialized if we can get that in first
Nothing from FormBase is actually being used if we change this to
implements FormInterfacethe test still passes
waitForElementVisible()doesn't actually fail if the element never appears it just returns NULL.So these lines pass even if you update
\Drupal\ajax_test\Form\AjaxTestMessageForm::makeMessageAlternate()to not pass the optional arg$messageWrapperQuerySelectorto the command.If the first line is just wrapped in
$this->assertNotEmpty()then thepageTextNotContains()is not needed.there are 2 other cases like this in the test.
Super nit. If the for loop started with
$i = 1we don't have to test for $i+1. Would also have to change to$i <= 6I think anytime we can avoid
assertWaitOnAjaxRequest()we should. I think it is better to explicitly wait for the thing we are expecting.This would work
This patch is not touching this file for anything so it feels unrelated. But good fix if we can get it in in this patch
Comment #17
oknateI'll make these changes this evening. Thanks for the review!
Comment #18
tedbowAlso I should have mentioned I think this is great new Ajax command!
Comment #19
phenaproxima#3052526: Drupal.Message.clear throws exception if messageWrapper not initialized is in, so this needs to be re-rolled too.
Comment #20
oknateResponding to #16:
1. ✅ Changed to "@var bool"
2. ✅ Added "@see Drupal.Message.defaultWrapper()"
3. ✅ Removed lines.
4. ✅ Added response.messageWrapperQuerySelector to the doc block.
5. 🎉 Removed this code!
6. ✅ changed to "implements FormInterface".
7. ✅ Updated to assert waitForElementVisible() doesn't return NULL. Since the check is repetitive, I added a helper function.
8. ✅ Updated loop to start at 1. Yes, that's nicer. Why didn't I think of that?
9. ✅ Thanks for the code there. Works great.
10. 👍
Comment #21
phenaproximaThis is going to need a change record for sure.
Comment #22
tedbow@oknate thanks all the changes look good.
Sorry I didn't catch 1 other thing before and 1 could have
This comment should wrap at 80 characters
super nit and probably doesn't need fixing. But if you agree and are fixing the other point
You can just return the result of
addCommand()as this returns the response itselfComment #23
oknateFixed nits in #22.
Comment #24
tedbow@oknate thanks. Looks good!
Setting to RTBC! 🎉
Test fail will bump but I think it will be good!
Comment #25
tedbowSorry forgot that we still need the change record. but the patch looks good
Comment #26
oknateHere's the change record:
https://www.drupal.org/node/3086403
Comment #27
oknateWhen committed, make sure @bjmnm gets credit for doing the initial development on this!
Comment #29
phenaproximaCrediting @bnjmnm per #9.
Comment #30
phenaproximaChange record looks good and detailed! Restoring RTBC as per #24, and removing the "Needs subsystem maintainer review" tag.
Thanks, @oknate and @tedbow! Let's land this.
Comment #31
phenaproximaTurns out @tedbow is not, according to MAINTAINERS.txt, a subsystem maintainer of the AJAX system. Whoops.
So...I'm restoring the "needs subsystem maintainer review" tag, but keeping RTBC. And I'm assigning this to @effulgentsia who, as a subsystem maintainer and framework manager, is probably in the best position to review this patch.
Comment #32
phenaproximaAlso crediting @tedbow for reviews.
Comment #33
wim leersI added specifically for the "message" aspect. I said that in #14, but should've been more explicit probably. I even quoted specifically those parts that I thought needed their sign-off.
The patch does not change the AJAX system. I'm a pseudo maintainer of the AJAX system too (having worked on the "AJAX page state" stuff a lot, as well as BigPipe, which uses the AJAX system too). So I feel comfortable reviewing this new AJAX command too.
🐪 These should not use camelCase. Only class properties use camelCase.
👍
AnnounceCommanduses the same pattern.👎
Command to … via AJAX command.I think we can do better. What aboutCommand to add a message to the message area.🤓
a default→🤓
TRUE→true(we only capitalize it in PHP, not in JS)🤓 Extraneous blank line.
Comment #34
oknateAddressing feedback in #33
1. ✅I also renamed the properties a bit. Using 'message' in the property names internally to this class is redundant.
etc.
3. ✅ Referencing AJAX command in an ajax command in ajax.es6.js is also redundant, so I like this better. 👍
4. ✅Good point, I misread the
Drupal.Message()::defaultWrappercode. There's only one default. The test coverage demonstrates this too.5. ✅Fixed.
6. ✅Fixed.
Comment #35
wim leersComment #36
phenaproximaSo, I see a problem here. The drupal.ajax library does not have a dependency on drupal.message, which means that this might result in a "Drupal.Message is undefined" error unless some other code has coincidentally added the drupal.message library to the page.
I imagine we do NOT want to have drupal.ajax depend on drupal.message, so I would suggest that we modify messages.es6.js so that, if Drupal.AjaxCommands is defined, it adds the `message` callback to Drupal.AjaxCommands.prototype. Something like:
Comment #37
tedbowre #36
The current pattern in the patch is the same one we used for #3020352: Create a new AnnounceCommand to call Drupal.announce on an AjaxResponse
Drupal.AjaxCommands.announce()should only ever be called by invoking the\Drupal\Core\Ajax\AnnounceCommandon the server and\Drupal\Core\Ajax\AnnounceCommand::getAttachedAssets()ensures that thecore/drupal.announcelibrary is attached.drupal.ajaxdoes not depend ondrupal.announcebut we can be assured the library will be attached ifDrupal.AjaxCommands.announce()is called.This is the same with
drupal.ajaxnot depending ondrupal. message. We can be assured that ifDrupal.AjaxCommands.message()is called thedrupal. messagelibrary will be loaded because\Drupal\Core\Ajax\MessageCommand::getAttachedAssets()includes it.All of the commands including the announce and message commands on
Drupal.AjaxCommandsrequireA developer would have to jump through a ton of hoops and really be making some bad decisions to actually construct these arguments from scratch outside of the server-side ajax command system that Drupal provides and call any of these JS command functions directly.
So I think it is safe to assume that any libraries the server-side command classes provide will be attached.
Comment #38
phenaproximaAh, okay. This is the thing I missed. Carry on!
Comment #39
andrewmacpherson commentedJust seeing this for the first time. Spotted it referenced in #3081526: Add announcement after clicking 'Save and select' in the media library..
What happens if an ajax response includes both a
MessageCommandand anAnnounceCommand. They will both result in calls toDrupal.announce(), right?In the old issue that introduced
Drupal.message, I pointed out that the use ofDrupal.announceis less than ideal, because:The possibility for an ajax responses to have a
MessageCommandand anAnnounceCommandcould get flaky and confusing if these aren't used carefully.Drupal.announce()should be used sparingly, I think. However I've noticed a trend over the last year or so towards treating it as the go-to solution for all your screen reader needs, and dumping arbitrary messages. Live regions shouldn't be treated like console output; that's not how they work at all (well,role="log"is intended to, but I don't think it's well supported yet).In the longer term, I think we should move
Drupal.messageoff ofDrupal.announce, so it treats messages as live regions in their own right. However we may need to wait for browser and assistive tech support to improve first. In theory, the benefit is that the assistive tech can handle a queue of multiple ARIA live regions, possibly based on user preferences.In the shorter term, it would be good to provide some kind of guidance about the difference between
AnnounceCommandandMessageCommand. In many cases you will want one or the other; seldom both. I don't fully know what that guidance should look like yet, but at minimum it would be along the lines of:"Developers should take care when using
MessageCommandandAnnounceCommandtogether in the same AJAX response. Manual testing with screen readers is recommended."Can we add something like that to change record, and class docs?
Comment #40
wim leersWow, I'm so glad you spotted that @andrewmacpherson! 😳 I should've thought of that too. Agreed with your recommendation!
Comment #41
oknateAn announce happens along with Drupal.Message::add() unless the options array is passed with options.announce is set to ''.
Comment #42
phenaproximaI would suggest this text for the class docs:
Also updating for explicit test coverage of this allowance.
Comment #43
oknateComment #44
oknateSmall comment change in a helper method.
Comment #45
tedbow@andrewmacpherson thanks for bring this up.
@oknate I think the changes look good
Back to RTBC!
Comment #46
andrewmacpherson commentedThe latest patch adds advice to the MessageCommand class docs.
Shouldn't we also add corresponding advice to the AnnounceCommand class too?
Comment #47
oknateGood point, @andrewmacpherson. Added a note on that command as well.
Comment #48
phenaproximaComment #49
larowlanComment #51
larowlanCommitted 7dde35c and pushed to 8.8.x. Thanks!
Published the change record
Comment #52
wim leersYay, this unblocked two issues!