Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce formal API for displaying notices #5975

Closed
danielbachhuber opened this issue Apr 4, 2018 · 19 comments
Closed

Introduce formal API for displaying notices #5975

danielbachhuber opened this issue Apr 4, 2018 · 19 comments
Labels
Backwards Compatibility Issues or PRs that impact backwards compatability [Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.

Comments

@danielbachhuber
Copy link
Member

danielbachhuber commented Apr 4, 2018

Core WordPress has an admin_notices action where it's quite common for plugins / themes to display notices.

In Gutenberg, this looks quite bad:

image

Gutenberg should have some formal API for plugins / themes to register their notices. This should support notices specific to the view (e.g. editor) and user actions (e.g. saving a post).

Previously: #5590 #5927 #3964

@danielbachhuber danielbachhuber added [Type] Enhancement A suggestion for improvement. [Feature] Extensibility The ability to extend blocks or the editing experience Backwards Compatibility Issues or PRs that impact backwards compatability labels Apr 4, 2018
@danielbachhuber danielbachhuber added this to the Merge Proposal milestone Apr 4, 2018
@jsmoriss
Copy link

jsmoriss commented Apr 5, 2018

FYI - My own plugins provide important notices to users after creating / updating the metabox. I need a way to submit notices to Gutenberg for display. I cannot make my plugins compatible with Gutenberg without this functionality. Here is the code I've started, but have stopped development because 1) I need to exclude auto-saves (don't want to refresh on auto-save), and 2) I need to submit notices to Gutenberg, which does not appear to be possible right now.

var editPost = wp.data.select( 'core/edit-post' ), lastIsSaving = false;

wp.data.subscribe( function() {
        var isSaving = editPost.isSavingMetaBoxes();
        if ( isSaving !== lastIsSaving && !isSaving ) {
                lastIsSaving = isSaving;

                // TODO: ajax call to get metabox HTML

                // TODO: refresh the metabox container

                // TODO: ajax call to get the notices array

                // TODO: submit notices to Gutenberg for display

        }
        lastIsSaving = isSaving;
} );

Thanks,

js.

@karmatosed karmatosed modified the milestones: Merge Proposal, Merge Proposal: Back Compat Apr 12, 2018
@adamsilverstein
Copy link
Member

For this task it would be worth considering adding direct support for add_settings_error (https://developer.wordpress.org/reference/functions/add_settings_error/) so existing uses will continue to work. I'm imaging an api similar to the customizer where the PHP size maps to a similar JS based api that plugins could also use.

@westonruter
Copy link
Member

westonruter commented Apr 17, 2018

Are not notices already supported? I've been using wp.data.dispatch( 'core/editor' ).createWarningNotice() successfully to create them in https://github.com/Automattic/amp-wp/blob/286381c/assets/js/amp-block-validation.js#L175-L185

@adamsilverstein
Copy link
Member

Right, we can add notices this way in JavaScript.... @danielbachhuber is this proposing adding a PHP side API?

Supporting the existing PHP API ( add_settings_error ) could be useful for error states that occur on the PHP side, in an endpoint, or at page load for example, or for plugins that may have no post JavaScript at all and still have a reason to display a notice (for example, an api required to save posts is not available).

@danielbachhuber
Copy link
Member Author

I was unaware of wp.data.dispatch( 'core/editor' ).createWarningNotice(). If there is such equivalent API, this may be a matter of documentation (or some magic transformation of the existing PHP admin notices into Gutenberg notices).

@jsmoriss
Copy link

Is createWarningNotice() unable to handle HTML? I'm getting an "TypeError: e.replace is not a function" error...

var noticeMsgHtml = data[noticeType][noticeNum];
var noticeElement = wp.element.createElement( 'div' );

noticeElement.innerHTML = noticeMsgHtml;

createWarningNotice( noticeElement );

Thanks,

js.

@adamsilverstein
Copy link
Member

@jsmoriss you can try following this pattern from guternberg internals:

dispatch( createSuccessNotice(
<p>
<span>{ noticeMessage }</span>
{ ' ' }
{ shouldShowLink && <a href={ post.link }>{ __( 'View post' ) }</a> }
</p>,
{ id: SAVE_POST_NOTICE_ID, spokenMessage: noticeMessage }
) );

@mtias
Copy link
Member

mtias commented Oct 3, 2018

Closing in favor of #6388 proposal.

@rgomezp
Copy link

rgomezp commented Feb 26, 2019

I have yet to find good documentation on how to do notices in Gutenberg. The best I could find was this one: https://github.com/WordPress/gutenberg/tree/master/docs/designers-developers/developers/tutorials/notices

However, it does not really describe how best to display a notice based on data from an asynchronous HTTP call. I want to grab data from the call, pass it to Javascript somehow (using wp_enqueue_script perhaps?), and get the notice to display with my custom content.

main.php:

$response = wp_remote_post($url, $request);
$status = $response['response']['code']
if($status==200) {
    // display notice 1
} else {
   // display notice 2
}

Any ideas on how best to do this? Thanks

@danielbachhuber
Copy link
Member Author

I want to grab data from the call, pass it to Javascript somehow (using wp_enqueue_script perhaps?), and get the notice to display with my custom content.

We did exactly this in our implementation.

Here's where we load the data into the window variable:

/**
 * Registers data we need client-side as a part of the initial page load.
 */
public static function action_enqueue_block_editor_assets() {
	$blocks_data = array(
		'editorNotices' => array(),
	);
	$post_id     = self::get_current_post_id();
	if ( $post_id ) {
		$blocks_data['editorNotices'] = Editor::get_converter_messages( $post_id );
	}
	wp_localize_script( 'tasty-recipes-block-editor', 'tastyRecipesBlockEditor', $blocks_data );
}

And here's where we create the notices client-side:

/**
 * WordPress dependencies
 */
const {
	dispatch,
} = wp.data;

/**
 * Fetches existing notice data and generates the corresponding notice.
 */
export default function generateNotices() {
	const { createNotice } = dispatch( 'core/notices' );
	window.tastyRecipesBlockEditor.editorNotices.forEach( ( notice ) => {
		createNotice( notice.type, notice.content, {
			isDismissible: notice.dismissible || false,
			actions: notice.actions || null,
		} );
	} );
}

@rgomezp
Copy link

rgomezp commented Mar 1, 2019

@danielbachhuber Thanks for the response Daniel. However, I didn't quite understand what will invoke the generateNotices function? I would think it shouldn't run until AFTER the data is available in the window variable (e.g: receives some sort of trigger from server as opposed to "as part of initial page load^^"). Also, should I add the javascript using wp_enqueue_script?

@danielbachhuber
Copy link
Member Author

However, I didn't quite understand what will invoke the generateNotices function? I would think it shouldn't run until AFTER the data is available in the window variable (e.g: receives some sort of trigger from server as opposed to "as part of initial page load^^"). Also, should I add the javascript using wp_enqueue_script?

Yes. It's ES6 so you'll need to hook up Webpack to transpile it down. You'll also need to set up wp_enqueue_script to enqueue it.

Here's the full starting point on using JavaScript: https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/javascript/

@davidfcarr
Copy link

davidfcarr commented Mar 6, 2019

@danielbachhuber I've had a similar challenge about finding a good tutorial on this, and I appreciate your code sample. One thing I don't understand is whether the code you're showing tells Gutenberg when the notice should be displayed.

My challenge is creating a notice for a specific post type that will be displayed when the post is saved. Actually, the display of this notice would be conditional -- it should only be shown if the post contains metadata marking it as a template. I can figure out the application logic, but I can't figure out how to

  • detect that a post has been saved
  • create a notice that will be displayed every time the post is published or updated (not autosaved), provided the application-specific conditions are met

You've explained how to create the notice, but I don't see how it's connected to something like a publish or update event.

I'm far enough into learning Gutenberg that I've created a number of custom blocks, but certain aspects such as notifications, modals remain mysteries. I've managed to do a few things with the wp.data state storage system, but that also remains a tough one for me to understand.

Any further clues about notices would be appreciated.

@danielbachhuber
Copy link
Member Author

You've explained how to create the notice, but I don't see how it's connected to something like a publish or update event.

Right — it's just connected to the page load event at the moment. Off the top of my head, I'm not sure of how to hook into a "Save Post" event. You'd need to do some research to sort that out.

@davidfcarr
Copy link

Thanks. At least that tells me it's not obvious.

@davidfcarr
Copy link

davidfcarr commented Mar 8, 2019

@danielbachhuber @rgomezp I've made progress figuring out how to display a notice triggered by an event such as post save. The routine below is loosely based on code Gutenberg uses internally to save metabox content on post save.

In my use case, certain posts of the type rsvpmaker are used as templates for specific events. The RSVPMaker plugin needs to display a notice prompting the user who has updated a template to click through to another screen if they want to create or update events based on that template. The url for the admin page where you do that is localized under the rsvpmaker_json variable. On the PHP side, we test that the post is of type rsvpmaker before outputting that variable.

My Gutenberg code tests whether the post is in the isSavingPost state but not an autosave.

const { subscribe } = wp.data;
if((typeof rsvpmaker_json !== 'undefined' ) && rsvpmaker_json.projected_url) {
		let wasSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
		let wasAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
		let wasPreviewingPost = wp.data.select( 'core/editor' ).isPreviewingPost();
		// determine whether to show notice
		subscribe( () => {
			const isSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
			const isAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
			const isPreviewingPost = wp.data.select( 'core/editor' ).isPreviewingPost();
			const hasActiveMetaBoxes = wp.data.select( 'core/edit-post' ).hasMetaBoxes();
			// Save metaboxes on save completion, except for autosaves that are not a post preview.
			const shouldTriggerTemplateNotice = (
					( wasSavingPost && ! isSavingPost && ! wasAutosavingPost ) ||
					( wasAutosavingPost && wasPreviewingPost && ! isPreviewingPost )
				);
			// Save current state for next inspection.
			wasSavingPost = isSavingPost;
			wasAutosavingPost = isAutosavingPost;
			wasPreviewingPost = isPreviewingPost;
			if ( shouldTriggerTemplateNotice ) {
	wp.data.dispatch('core/notices').createNotice(
		'info', // Can be one of: success, info, warning, error.
		__('After updating this template, click'), // Text string to display.
		{
			isDismissible: true, // Whether the user can dismiss the notice.
			// Any actions the user can perform.
			actions: [
				{
					url: rsvpmaker_json.projected_url,
					label: __('create / update events')
				}
			]
		}
	);
			}
			/* placeholder for logic to remove notice
			else {
				console.log('remove notice');
			}
			*/
} );	
}

This works pretty well. One lingering issue is that if you save the post multiple times, the notice gets output multiple times. If you can suggest a way of preventing that from happening, I'd like to hear it.

@rgomezp
Copy link

rgomezp commented Apr 15, 2019

@davidfcarr @danielbachhuber ,
I've been wondering the same thing. I get duplicate notices (especially after release of WP 5.1.1 for some reason) and would easily be able to fix this if I could just replace one notice with the other so the latest is the only one to show.

Thanks

@davidfcarr
Copy link

Here's the current code I'm using, which eliminated the issue of the redundant messages being displayed. Adding an ID parameter to the notification prevents it from being added repeatedly. This version also uses multiple action parameters to present two alternatives with the word "or" in between.

This appears as an additional message after the default "Post saved" message. Ideally, I might want to either delete or modify that notification. I know there is a remove notification command, but I'm not sure how to determine the ID of that default notification.

if((typeof rsvpmaker_json !== 'undefined' ) && rsvpmaker_json.projected_url) {
let wasSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
let wasAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
let wasPreviewingPost = wp.data.select( 'core/editor' ).isPreviewingPost();
// determine whether to show notice
subscribe( () => {
const isSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
const isAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
const isPreviewingPost = wp.data.select( 'core/editor' ).isPreviewingPost();
const hasActiveMetaBoxes = wp.data.select( 'core/edit-post' ).hasMetaBoxes();
 // trigger on save completion, except for autosaves that are not a post preview.
 const shouldTriggerTemplateNotice = (
 ( wasSavingPost && ! isSavingPost && ! wasAutosavingPost ) \|\|
 ( wasAutosavingPost && wasPreviewingPost && ! isPreviewingPost )
 );
 // Save current state for next inspection.
wasSavingPost = isSavingPost;
wasAutosavingPost = isAutosavingPost;
wasPreviewingPost = isPreviewingPost;
if ( shouldTriggerTemplateNotice ) {
var newurl = rsvpmaker_json.projected_url.replace('template_list','setup');
wp.data.dispatch('core/notices').createNotice(
'info', // Can be one of: success, info, warning, error.
__('After updating this template, click'), // Text string to display.
{
id: 'rsvptemplateupdate', //assigning an ID prevents the notice from being added repeatedly
isDismissible: true, // Whether the user can dismiss the notice.
// Any actions the user can perform.
actions: [
{
url: newurl,
label: __('New Event based on template'),
 },
 {
 label: ' or ',
 },
 {
 url: rsvpmaker_json.projected_url,
 label: __('Create / Update events'),
  },
  ]
  }
  );
  }
  } );
  }

@davidfcarr
Copy link

Here's what that looks like

rsvp-notification

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backwards Compatibility Issues or PRs that impact backwards compatability [Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests

8 participants