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

Support custom validation by allowing bypass of the Publish/Update button saving process #49811

Closed
wants to merge 6 commits into from

Conversation

sc0ttkclark
Copy link

@sc0ttkclark sc0ttkclark commented Apr 13, 2023

What?

This PR adds a new filter in the click handler for the Publish / Save button that can stop the save from proceeding.

Why?

I've searched high and low for code that would allow me to keep the Save / Publish button active initially so that upon interacting with the button it would trigger some additional validations before bailing on the save and then subsequently enabling save locking.

Ultimately, the only way around this is to listen to the click event on the button itself and then bail there.

I believe this could be handled much more directly within the component through a new filter which would allow a better approach towards running any custom validations that need to happen before allowing the save to continue.

How?

It uses a filter and also allows for utilizing the properties for the button which give easy access to booleans and other context towards whether the save should be bypassed.

Testing Instructions

  1. Open add new post or edit a post
  2. Click to save using the Publish button or the Update button
  3. With code in place for the filter, you can stop the save and show notices however you'd like. You can also enable post save locking to prevent saving visually at this stage until the validation errors are resolved.

Testing Instructions for Keyboard

N/A

Screenshots or screencast

N/A

I've searched high and low for code that would allow me to keep the Save / Publish button active initially so that upon interacting with the button it would trigger some additional validations before bailing on the save and then subsequently enabling save locking.

Ultimately, the only way around this is to listen to the click event on the button itself and then bail there.

I believe this could be handled much more directly within the component through a new filter which would allow a better approach towards running any custom validations that need to happen before allowing the save to continue.
@github-actions
Copy link

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @sc0ttkclark! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@github-actions github-actions bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Apr 13, 2023
@sc0ttkclark
Copy link
Author

You can use wp.data.subscribe() to attempt to run custom validation / locking / notice logic but at that point it will always be too late to stop the save.

When you interact with the Publish / Update button, it will have already started saving at the point in which you can work with the subscribe method.

@sc0ttkclark
Copy link
Author

sc0ttkclark commented Apr 13, 2023

Example code using the new filter

function checkValidation() {
	if ( isHandlingValidation ) {
		return false;
	}

	isHandlingValidation = true;

	const isSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
	const isPublishingPost = wp.data.select( 'core/editor' ).isPublishingPost();
	const isAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
	const hasLock = wp.data.select( 'core/editor' ).isPostSavingLocked();
	const notices = wp.data.select( 'core/notices' ).getNotices();

	let hasNotice = false;

	notices.every( (notice) => {
		if (notice.id === 'customValidationLock') {
			hasNotice = true;

			return false;
		}
	} );

	if ( !validateCustomField() ) {
		if ( !hasLock ) {
			wp.data.dispatch( 'core/editor' ).lockPostSaving( 'customValidationLock' );
		}

		wp.data.dispatch( 'core/notices' ).createErrorNotice( 'Custom Validation Field is required!', {
			id : 'customValidationLock',
			isDismissible: false,
		} );

		isHandlingValidation = false;

		return false;
	}

	wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'customValidationLock' );
	wp.data.dispatch( 'core/notices' ).removeNotice( 'customValidationLock' );

	isHandlingValidation = false;

	return true;
}

wp.hooks.addFilter( 'editor.PostPublishButton.shouldSubmit', (shouldSubmit) => {
	return shouldSubmit ? checkValidation() : false;
} );

Example workaround without the filter that uses wp.data.subscribe() and does not stop save

let isHandlingValidation = false;

wp.data.subscribe( () => {
	if ( isHandlingValidation ) {
		return;
	}

	isHandlingValidation = true;

	const isSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
	const isPublishingPost = wp.data.select( 'core/editor' ).isPublishingPost();
	const isAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
	const hasLock = wp.data.select( 'core/editor' ).isPostSavingLocked();

	if ( !validateCustomField() ) {
		if ( !isAutosavingPost && (isSavingPost || isPublishingPost) ) {
			if ( !hasLock ) {
				wp.data.dispatch( 'core/editor' ).lockPostSaving( 'customValidationLock' );
			}

			wp.data.dispatch( 'core/notices' ).createErrorNotice( 'Custom Validation Field is required!', {
				id : 'customValidationLock',
			} );
		}
	}
	else {
		wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'customValidationLock' );
		wp.data.dispatch( 'core/notices' ).removeNotice( 'customValidationLock' );
	}

	isHandlingValidation = false;
} );

Example workaround without the filter that uses a hacky click event

let initListener = false;
let inited = false;

function checkValidation() {
	if ( isHandlingValidation ) {
		return false;
	}

	isHandlingValidation = true;

	const isSavingPost = wp.data.select( 'core/editor' ).isSavingPost();
	const isPublishingPost = wp.data.select( 'core/editor' ).isPublishingPost();
	const isAutosavingPost = wp.data.select( 'core/editor' ).isAutosavingPost();
	const hasLock = wp.data.select( 'core/editor' ).isPostSavingLocked();
	const notices = wp.data.select( 'core/notices' ).getNotices();

	let hasNotice = false;

	notices.every( (notice) => {
		if (notice.id === 'customValidationLock') {
			hasNotice = true;

			return false;
		}
	} );

	if ( !validateCustomField() ) {
		if ( !hasLock ) {
			wp.data.dispatch( 'core/editor' ).lockPostSaving( 'customValidationLock' );
		}

		wp.data.dispatch( 'core/notices' ).createErrorNotice( 'Custom Validation Field is required!', {
			id : 'customValidationLock',
			isDismissible: false,
		} );

		isHandlingValidation = false;

		return false;
	}

	wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'customValidationLock' );
	wp.data.dispatch( 'core/notices' ).removeNotice( 'customValidationLock' );

	isHandlingValidation = false;

	return true;
}

wp.data.subscribe( () => {
	const publishButton = document.querySelector( '.editor-post-publish-button__button' );

	if (!publishButton) {
		return;
	}

	if (!initListener) {
		initListener = true;

		publishButton.addEventListener( 'click', function ( e ) {
			inited = true;

			if (checkValidation()) {
				return true;
			}

			e.preventDefault();
		} );
	}

	if (inited) {
		checkValidation();
	}
} );

@sc0ttkclark
Copy link
Author

For reference, plugins like ACF have to override the click event to prevent the block editor from saving when it has their own validations that fail. That's the same implementation that a plugin like Pods will also have to implement to address this.

@sc0ttkclark sc0ttkclark changed the title Support custom validation by allowing disabling of the save itself Support custom validation by allowing bypass of the Publish/Update button saving process Apr 14, 2023
@Mamaduka Mamaduka self-requested a review April 24, 2023 06:29
@sc0ttkclark
Copy link
Author

@Mamaduka Let me know if you notice any areas I can improve here. This is my first contribution for the Gutenberg project via PR and I'm sure I've done something wrong somewhere :) I'm also unsure how to approach a test for this.

@Mamaduka
Copy link
Member

Hi, @sc0ttkclark

I believe the "Publication Checklist" plugin achieves similar behavior by enabling the pre-publication checklist and using the "lock saving" actions.

There was a similar proposal (#10115), which was superseded by save-locking actions.

@skorasaurus skorasaurus added [Feature] Extensibility The ability to extend blocks or the editing experience [Feature] Saving Related to saving functionality labels May 9, 2023
@sc0ttkclark
Copy link
Author

@Mamaduka For reference to the original issue I encountered that led me here -- I attempted to use the post locking feature but I was unable to find any workable way to stop the post from saving when they click to Update the post.

The subscribe call lets me hook in and lock the post but the save has already started/completed at that point.

Copy link

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: sc0ttkclark <sc0ttkclark@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@@ -157,9 +158,15 @@ export class PostPublishButton extends Component {
}

const onClickButton = () => {
// Allow for overriding saving for custom validations.
if (!applyFilters('editor.PostPublishButton.shouldSubmit',true,this.props)) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static analysis CI has me a little confused as to what change needs to be made here but I'm happy to make that change to get it passing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not sure here -- I've adjusted it a few times but taking out spaces doesn't seem to get it passing and the https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/ code standards themselves aren't passing so I'm 🤷

@sc0ttkclark sc0ttkclark closed this by deleting the head repository Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience [Feature] Saving Related to saving functionality First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants