Open elliotcondon opened 5 years ago
@noisysocks Adding Gutenberg compatibility for PHP/AJAX Validation will be our next focus.
Thanks for the info provided over on the original ticket. I added an extra note in the topic above: "It is important for UX that we validate the form data only when the user clicks the "publish" button."
Can we work on a solution for validating the form data via AJAX that also keeps the above in mind?
Copying some answers over from https://github.com/WordPress/gutenberg/issues/12692#issuecomment-452165743.
- How can we get all the
$_POST
data about the post (title, content, etc)?
select()
and getEditedPostAttribute()
are what we want again for this. The REST API reference is useful here for figuring out what post attributes are available.
const { getEditedPostAttribute } = wp.data.select( 'core/editor' );
const title = getEditedPostAttribute( 'title' );
const content = getEditedPostAttribute( 'content' );
- Is there an API for displaying an error message in Gutenberg?
Yes, the Notices API is what we'll want here. Namely, createErrorNotice()
.
wp.data.dispatch( 'core/notices' ).createErrorNotice( 'Something is not good!' );
Can we work on a solution for validating the form data via AJAX that also keeps the above in mind?
Got it. I don't think this is currently possible but it's a new hook that we can discuss adding over in the Gutenberg project.
To clarify: do we need to hook into when the post is being saved, or when the post is being published?
Hi @elliotcondon
We validate the custom field values only when publishing a post, or when updating a published post. Autosaves and drafts are not validated.
At the moment, we listen to the "form submit" action, and "prevent default" to stop the form from submitting. We then validate the form data via AJAX, and either display errors or re-submit the form (but this time allow the event to run).
My current thinking is that ACF could use PluginPrePublishPanel
to render a component in the pre-publish panel that:
lockPostSaving()
when the component is mounted so that the user can not click Publish.Spinner
) to indicate to the user that the post is currently being validated.unlockPostSaving()
.Very quick mockup of what this could look like:
Does that make sense? Would this be a workable approach?
Hi @noisysocks
This is a great idea, but will this also work for updating a published post?
Hey @elliotcondon. That's a good point. No, unfortunately this won't handle the case where a published post is updated. We may have to add a new filter for this after all. I've suggested this over in https://github.com/WordPress/gutenberg/issues/13413.
Hi @noisysocks
Just following up on this one. Did we get anywhere with a solution to "validate" and "prevent" the form from being submit?
If not yet explored, would it be possible to use a promise object + a JS filter to allow 3rd party (or core) logic to stop/trigger the saving process?
Hey @elliotcondon! I like your new avatar π
I haven't been actively working on this. https://github.com/WordPress/gutenberg/issues/13413 tracks adding the API which I prefer which is a promise object + JS filter. https://github.com/WordPress/gutenberg/pull/13718 is an unrelated PR that also had a need for such an API.
@talldan: What's the status on https://github.com/WordPress/gutenberg/pull/13718? Do you think we can include the mentioned API?
Any update here?
@matgargano - Not from my end. I'm still waiting for WordPress to add in some sort of action/filter to hook in and customize the saving process.
Is there a ticket on the Wordpress side we can comment on/contribute to?
@benbowler - There are a few GitHub threads listed in previous replies on this thread.
If you're reading this via email, click the GitHub link to view this issue in your browser so you can scroll up and check those links.
For example: https://github.com/WordPress/gutenberg/issues/13413
Hi all, Do we have any update here?
Hi @elliotcondon,
Any update on this? As the WordPress 5.3 has been released, You probably able to fix the issue now.
Best Jenil
@jenilk I'm not aware of any changes in WP 5.3 that introduced an API for PHP validation. If you can provide some reference, that would be great :)
Hi @elliotcondon I am also in a same phase as you're. However, I have created a ticket to WordPress where we can all contribute there. As this issue goes to a blocker we need to find the solutions as earliest.
Ticket: https://core.trac.wordpress.org/ticket/48959
Best Jenil
Hi @elliotcondon - although there is no specific API for PHP side validation, you should be able to achieve something close to what you want with the existing extensibility provided in Gutenberg (unless I am missing something):
If this doesn't work for your use case, can you explain why? Can you identify exactly what type of hook or filter in GB would enable your use case?
This plugin might be useful to look at: https://github.com/humanmade/publication-checklist
Hi @adamsilverstein
Thanks for the information. The "Post saving lock" API and the example plugin look very promising and I'll begin some testing with this shortly!
Hey, I was using non Gutenberg fo so long, but I decided to use ... some part of if. Today I tested it, I make it a bit without content, just title ...
But I noticed this issue ... and I was a bit not happy of it. So I fixed it. I tested it on ACF PRO 5.8.8 and WP 5.3.2, I did not test every use case yet. But seems like working, hope it will help someone.
function enhancement_gutenberg_isu(){
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
wp.domReady(function(){
//acf Custom Validation for Gutenberg
var postSaveButtonClasses = '.editor-post-publish-button';
$(document).on('click', postSaveButtonClasses , function(e){
e.stopPropagation();
e.preventDefault();
var $button = $(this)
acf_validate_fields($button);
})
})
function acf_validate_fields($button){
var editorInfo = wp.data.select( 'core/editor' );
var isPublishSidebarOpened = wp.data.select('core/edit-post').isPublishSidebarOpened();
var tmpButtonText = $button.text();
wp.data.dispatch( 'core/editor' ).lockPostSaving( 'acfValidateFields' );
//$button.text('Fields validation ...');
return acf.validateForm({
form: jQuery('#editor'),
reset: true,
failure: function( $form, validator ){
var notice = validator.get('notice').data.text
wp.data.dispatch( 'core/notices' ).createErrorNotice( notice, { id: 'ACF_VALIDATION', isDismissible: true} );
if(isPublishSidebarOpened){
wp.data.dispatch('core/edit-post').closePublishSidebar()
}
},
complete: function( $form, validator ){
//$button.text(tmpButtonText)
wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'acfValidateFields' );
},
success: function(){
//remove notices if there are any from acf validation
wp.data.dispatch( 'core/notices' ).removeNotice('ACF_VALIDATION');
//save post if is ok to save
if(editorInfo.isEditedPostSaveable()){
if(!editorInfo.isCurrentPostPublished() && ~['draft','auto-draft'].indexOf(editorInfo.getEditedPostAttribute( 'status' ))){
//for some reason if post is draft we must update manually post status
//otherwise post will be saved but not published;
wp.data.dispatch('core/editor').editPost({status: 'publish'})
}
wp.data.dispatch( 'core/editor' ).savePost()
}
}
})
}
});
</script>
<?php
}
add_action('admin_footer', 'enhancement_gutenberg_isu');
I have learned I can subscribe for editor ... but I think this way is much easier :D EDIT: issue with button naming, need more tweaks, but not important feature
Hi @isuke01
Thank you very much for your contribution here. I too have thought about using a click event listener, but ultimately scrapped the idea as it would not comply with Gutenberg's keyboard accessibility features.
If you are able to find a more holistic way to listen for (and prevent) the "save process", this would be greatly appreciated!
Thanks @elliotcondon,
yes, I totally forgot about that thing! I'll look into it.
EDIT: Actually I can't solve it using subscribe events, it is too late to prevent a save. For now I could not find any better solution, since there is no pre_save hook or something for Gutenberg yet. When they fix that ... if they fix that we can update it :D
Anyway I have solved that thing using wp.keycode
api.
Her is my a bit updated code (this example also contain example of required Featured image)`
function enhancement_gutenberg_isu(){
$imageIsRequired = __('Featured image is required', 'wpvue')
?>
<script type="text/javascript">
jQuery(document).ready(function($) {
//Make sure wp.Dom is ready
wp.domReady(function(){
//TODO: Check if it is post type where we want to use this logc
//check if there is data
if(!wp.data) return null;
var removeElement = wp.data.dispatch( 'core/edit-post').removeEditorPanel
//acf Custom Validation for Gutenberg
gutenbergWatchEventsSave()
})
/**
* Watch events of Gutenberg
*/
function gutenbergWatchEventsSave(){
var postSaveButtonClasses = '.editor-post-publish-button';
//Handle button
$(document).on('click', postSaveButtonClasses , function(e){
saveValidations(e)
})
//Handle keyshorts
document.addEventListener('keydown', function(e){
//https://developer.wordpress.org/block-editor/packages/packages-keycodes/
// remove other bubbling element in DOM
e.stopImmediatePropagation()
if(wp.keycodes.isKeyboardEvent.primary(e, 's')){
saveValidations(e)
}
}, true);
}
/**
* Just trigger validations required to save post
*/
function saveValidations(e){
e.stopPropagation()
e.preventDefault()
var hasImage = requiredFeaturedImage(['recipe', 'post', 'clinic'])
if(hasImage){
acf_validate_fields();
}
}
/**
* Make featured image required
*/
function requiredFeaturedImage($types){
var isImage = true;
var editor = wp.data.select('core/editor')
var currentPostType = editor.getCurrentPostType()
if(~$types.indexOf(currentPostType)){
var featuredImage = editor.getEditedPostAttribute('featured_media');
if(!featuredImage || featuredImage === 0){
//Remvoe previous notices
wp.data.dispatch( 'core/notices' ).removeNotice('REQUIRE_FEATURED_IMAGE');
wp.data.dispatch( 'core/editor' ).lockPostSaving( 'featuredImageIsRequired' );
wp.data.dispatch( 'core/notices' ).createErrorNotice( '<?php echo $imageIsRequired ?>', { id: 'REQUIRE_FEATURED_IMAGE', isDismissible: true} );
isImage = false;
setTimeout(function() {
wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'featuredImageIsRequired' );
}, 150);
}
}
return isImage;
}
/**
* Gutenberg Save action trigger
*/
function gutenbergSavePost(){
var editor = wp.data.select('core/editor')
if(editor.isEditedPostSaveable()){
if(!editor.isCurrentPostPublished() && ~['draft','auto-draft'].indexOf(editor.getEditedPostAttribute( 'status' ))){
//for some reason if post is draft we must update manually post status
//otherwise post will be saved but not published;
wp.data.dispatch('core/editor').editPost({status: 'publish'})
}
wp.data.dispatch( 'core/editor' ).savePost()
}
}
/**
* ACF Validation
*/
function acf_validate_fields(){
var editorInfo = wp.data.select( 'core/editor' );
var isPublishSidebarOpened = wp.data.select('core/edit-post').isPublishSidebarOpened();
wp.data.dispatch( 'core/editor' ).lockPostSaving( 'acfValidateFields' );
return acf.validateForm({
form: jQuery('#editor'),
reset: true,
failure: function( $form, validator ){
var notice = validator.get('notice').data.text
wp.data.dispatch( 'core/notices' ).createErrorNotice( notice, { id: 'ACF_VALIDATION', isDismissible: true} );
if(isPublishSidebarOpened){
wp.data.dispatch('core/edit-post').closePublishSidebar()
}
},
complete: function( $form, validator ){
wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'acfValidateFields' );
},
success: function(){
//remove notices if there are any from acf validation
wp.data.dispatch( 'core/notices' ).removeNotice('ACF_VALIDATION');
//save post if is ok to save
gutenbergSavePost()
}
})
}
});
</script>
add_action('admin_footer', 'enhancement_gutenberg_isu');
EDIT: wp.data check
Hi @isuke01
Thanks for looking into the keyboard shortcut issue. I'll be sure to test out something similar. Covering the "click" and "key" events should work for the most part, but I wonder if there are any other situations where the "save" function is called by WP core... perhaps when previewing, etc?
Hi @elliotcondon
Actually one more save of post that should we take care is switch from any status to private
but not all the way around. For some reason save is called if you switch from "Public" to "Private" only. It also display document alert, I have no clue why it is solved like that, looks strange.
Another thing. "Preview" it does not call save for current post, so no problem but I noticed Preview in this case is not saving any meta. (Tested on Posts, not custom CTP), maybe because fields are not register_meta
, I don't know. Also functions related on save for $_POST are not gonna work. .... AHgghh F Gutenberg ;{
Edit: I did more tests, looks like even meta with register_meta
and register in test are not saved, also auto save don't save any meta =,="
And about clicking "mod+s" shortcut to save, it works only when something is edited. But actually this does not work on ACF fields, so if you edit something inside ACF only, not in Gutenberg container, you're unable to save post using key commands. BUT! Since in my above function I'm not checking for isEditedPostDirty()
, it solves this issue π
Would be nice to have pre_save_hook
for Gutenberg but yet, I cant find solution for that.
Maybe someday! π
Hi @isuke01
Thank you for all the extra info!
It looks like we are not the only ones looking for a solution to this problem. Here are some GithUb issues you might be interested in looking over / following:
I agree that the best case scenario would be a pre_save_hook
for Gutenberg. I wonder if this is something that you might be able to help with? Your knowledge of the Gutenberg editor and API seem very strong.
If you have some free time, please read over those GitHub issues mentioned above, and let me know if you see anything that might be useful π.
Hey @elliotcondon
Thanks, actually my first experience with Gutenberg was 4 days ago, and I just spend few hours on it ... and it was painfully (I hope they will fix docs at some point it is agrrggrr)π I just cant deliver to client something that does not work so I tried quick fix it.
Anyway I have find out Gutenberg is not saving meta fields during preview/autosave/auto-draft save. WordPress/gutenberg/#20755.
So basically until I find a way how to solve saving those meta for preview, Gutenberg is not usable for me :/ Thing is, without Gutenberg everything works fine.
Im out of time to solve this issue by now, I'll look into that issues as soon as I can find some time, but for next couple of days Im totally occupied, and simply I'll use classic editor instead for now :)
hi @isuke01
I was so in trouble, tried your code. It may be a known problem for you, Paragraph Does not work new line by below code.
//Handle keyshorts
document.addEventListener('keydown', function(e){
//https://developer.wordpress.org/block-editor/packages/packages-keycodes/
// remove other bubbling element in DOM
e.stopImmediatePropagation()
if(wp.keycodes.isKeyboardEvent.primary(e, 's')){
saveValidations(e)
}
}, true);
@ituki Hey, sorry I'm really busy now and I can't do more tests etc, even if I wish :/ (my normal work is remote so quarantine does not help me with time, I have even more xD)
For sure there is more issues, like you should check if event wp. domReady
exist because it can lead you to problems on non gutenberg pages like options ... you may also check if this is post with gutenberg scree ... to avid code on any other pages than one with gutenberg ...
But to your problem.
Yeh, I see I canceled all input using e.stopImmediatePropagation()
before check, you may try move it inside check of save shortcode, like
if(wp.keycodes.isKeyboardEvent.primary(e, 's')){
e.stopImmediatePropagation()
saveValidations(e)
}
So it should cancel only save action. :) My whole code is more like proof of concept than real use. But I hope you can find something helpfully in this. Btw. There is hook, cant remember which one. But you can disable user keyboard key shortcuts using some WP hooks. So you can always try to do it if that is important issue :D
Hi all π,
Thank you so much for your support via this thread. There are some great code snippets, ideas and solutions here - they are definitely helping!
I might have an early prototype of a validation solution to share. Its a little rough around the edges, but the main functionality appears to be working well π. Feel free to test this out, expand on it and discuss ideas!
(function($, undefined){
var gutenbergValidation = new acf.Model({
wait: 'ready',
initialize: function(){
// Customize Gutenberg editor only.
if( acf.isGutenberg() ) {
this.customizeEditor();
}
},
customizeEditor: function(){
// Extract vars.
var editor = wp.data.dispatch( 'core/editor' );
var notices = wp.data.dispatch( 'core/notices' );
// Reference original method.
var savePost = editor.savePost;
// Override core method.
editor.savePost = function(){
// Validate the editor form.
var valid = acf.validateForm({
form: $('#editor'),
reset: true,
complete: function( $form, validator ){
// Always unlock the form after AJAX.
editor.unlockPostSaving( 'acf' );
},
failure: function( $form, validator ){
// Get validation error and append to Gutenberg notices.
var notice = validator.get('notice');
notices.createErrorNotice( notice.get('text'), {
id: 'acf-validation',
isDismissible: true
});
notice.remove();
},
success: function(){
// Save post on success.
savePost();
}
});
// Lock the form if waiting for AJAX response.
if( !valid ) {
editor.lockPostSaving( 'acf' );
return false;
}
// Save post as normal.
savePost();
}
}
});
})(jQuery);
@elliotcondon Hi! It seems to work well! Is there any ETA of including in the plugin core? Thanks! :)
@Emiliano-Bucci That's great news! Thanks for testing out this prototype. My plans are to include this in version 5.9.0, which will provide us with a beta + RC workflow allowing developers to test and identify issues with this approach.
5.9.0 is probably a few weeks away from a beta announcement. For now, if this solution works for you, please include it in your theme files until then :)
Looks great! Thanks! I have run few use cases and it solved all thanks :)
Interesting approach! I didn't realize you could overwrite the editor.save method in that way. Nice work figuring that out :) I'm still a little surprised you weren't able to find a workable solution using the subscribe approach, still I'm happy you have figured out a way to address your use case.
We also tested Elliot's code, and it was tested and confirmed as working across about 15 different page templates and two custom post types. I'd presume our tester tested it with the custom ACF validation hooks we use, too. I'd guess that covered about 30 field groups and about 100 individual fields, including group, image & repeater fields.
I've been emailing with Elliot behind the scenes after I got a similar fix to the above working and tested and I hoped to get it into ACF core (Elliot's similar solution here is probably better), and thought I'd share a related bit. I built similar code for ACF block validation (shown in the attached screenshots) that works by doing similar validation during the block's Javascript save function and slightly modifying the validate_values and acf_validate_save_post PHP functions in validation.php to be like the below so it can account for the block prefixes that get POSTed. I guess the good news is it looks like block validation may be implemented somewhat soon / eventually into ACF, too. I'm planning to work on reworking the code to just validate on update-button-click, and am excited about how the new validation code is making migrating ACF sites into Gutenberg + ACF/ACF-block sites so much more easy and possible. I also think Automattic should make unsolicited buyout offers to ACF, but I guess that's another topic. :) Thanks!
function acf_validate_save_post() {
// bail early if no $_POST
if( empty($_POST['acf']) ) return;
// validate
//OLD / prior- acf_validate_values( $_POST['acf'], 'acf' );
acf_validate_values( $_POST, 'savepost' );
}
function acf_validate_values( $values, $input_prefix = '' ) {
// bail early if empty
if( empty($values) ) return;
// different handling of validation for post saving implemented to be able to handle blocks
if ( 'savepost' == $input_prefix ) {
foreach ( $values as $postkey => $postvalue ) {
// loop -- modified to handle block prefixes
if ('acf' == $postkey || 'acf-block' == substr( $postkey, 0, 9 ) ) {
foreach( $postvalue as $key => $value ) {
// vars
$field = acf_get_field( $key );
//OLD $input = $input_prefix . '[' . $key . ']';
///NEW to handle block prefixes
$input = $postkey . '[' . $key . ']';
// bail early if not found
if( !$field ) continue;
//echo "found";
// validate
acf_validate_value( $value, $field, $input );
}
}
}
// If not specifically validating for a post save event, handle validation like before for backwards compatibility
} else {
foreach( $values as $key => $value ) {
// vars
$field = acf_get_field( $key );
$input = $input_prefix . '[' . $key . ']';
// bail early if not found
if( !$field ) continue;
//echo "found";
// validate
acf_validate_value( $value, $field, $input );
}
}
}
I can't seem to get Elliot's version to work. I've tried tweaking it a little, but I can't seem to get anything to stop the publish/update process. What am I missing?
I had the same experience as @todd-uams . Tried the snippet posted by @elliotcondon a few days ago but no luck.
How should we add that to our current existing WP projects?
Hi @todd-uams amd @elvismdev.
Thanks for the feedback. Would you be able to perform some basic JS debugging through the code to better articulate what is working / failing?
I'd be happy to. Is there something specific.
When I initially tried inserting the code, as is. I received a $ is not a function error. I tried changing the $('#editor') to JQuery('#editor'). This got rid of the error, but I'm not getting any response in Gutenberg. I've tried using it as an enqueued file and adding directly to the admin footer.
@todd-uams Thanks for the feedback. I've just updated the example to take care of the $
!== jQuery
issue.
By "not getting any response", are you referring to the AJAX call? Or the modified editor.savePost
function? If you could debug the JS, that would be super helpful to understand which parts are and aren't working for you.
I created a new clean install with just ACF Pro. For verification, I installed Gutenberg after testing to verify that it wasn't required. The results were the same. The block is just 4 fields. Text (required), True/False, Text Area (required), and a select field. The block just displays the information from the fields.
So, the new code doesn't have any errors, but it still won't throw any validation errors. I have two fields that are required. If I run the field group outside of Gutenberg, it throws the standard validation errors. When I use it as a block, it just saves the data with empty fields. There are no console errors with the save, so it isn't an outside error.
I added a few console log lines to see if everything is firing as expected. The acf gutenbergValidation model is running. There is a single firing of !valid
while it waits for acf Ajax (I believe). The success state fires next, followed by the complete state. I grabbed the data from the complete state, both for $form
and validator
. Both fire without errors.
@elliotcondon I'm not sure where else to look. I'd be happy to send any other information along.
Hi @elliotcondon, I added the code (https://github.com/AdvancedCustomFields/acf/issues/113#issuecomment-616949241) in 'acf-input.min.js', just for a test and it worked perfectly.
@altendorfme Thanks for the feedback!
@todd-uams Please note this validation improvement is not for "ACF Blocks". The code mentioned above will validate metaboxes.
Sorry for the mixup. The code did work for metaboxes on a Gutenberg page. Thanks
@elliotcondon I'll look at @benfrem 's code for a potential solution for ACF Blocks.
@todd-uams Fantastic! Block validation will be coming soon, but only after metabox validation is considered stable :)
I can confirm that a mixture of the code above works great when using 'edit' mode. It also works when in 'preview' mode and editing a specific block, but only validates that block and not the others. Thanks!
However, I am still looking for a way to get this working in 'preview' mode for all blocks. @elliotcondon I know your working on a solution (Thank You), but since we don't have an ETA is there any way that I can get all the fields and their values with the ACF JS API or with Gutengergs JS API so I can just perform a basic validation and loop through them and if the 'required' attribute is set and value is empty then I can set a notice. I am not really needing a way to open the block settings. I think I will just use some css to add a red outline around the wp-block that has the error so the user knows which block to edit. So a way to reference the error and block would be helpful. The locking and preventing updates and publish is working. I just need to validate all of the fields, but I find it difficult to figure out the gutenberg api and what options there are.
@elliotcondon I have downloaded v5.9.0-beta1.
The Preview mode Validation does work, but only when viewing the block being edited and when the Settings window is open. If I start to edit another block with passing validation and Publish the Page no Validation Errors get reported for the blocks that do not pass Validation. Also if I close the Settings window then no Validation errors get show. It should still report the Validation Errors and prevent the post from being updated.
[EDITED] Never mind, I forgot to remove my custom acf validation code :( v5.9.0-beta1 Validation does not work for me at all. No Validation errors are getting reported not even in 'edit' mode.
Also I think when there is an error on a block there should be a class added to the "wp-block" tag or the "acf-block-component" tag like 'acf-validation-error' and some sort of error indicator like a red outline etc. That way the User can see which blocks have errors and click on them to resolve. Probably only needed for 'preview' mode or 'auto' mode when block is in preview state. I am writing a custom class for my clients so I will most likely override the styles for my own and that is why a css class is important for me.
Let me know if there is a better place to report issues for v5.9.0-beta Thanks
Hi @ggedde. Please note the "Gutenberg Validation" feature in 5.9.0-beta1 is for metaboxes only - not "ACF Blocks".
Block validation will be coming soon in a future update π.
@elliotcondon Any updates on block validation?
Description
A highly used feature in ACF is our PHP validation powered via AJAX. This allows developers to highly customize the validation process via PHP whist providing convenient feedback to the user when a field value does not meet its requirements.
To do this, we use JS to intercept the "submit" action on the form and stop it. We then send all
$_POST
data via an AJAX request and use the returned JSON to either display errors or re-submit the form.It is important for UX that we validate the form data only when the user clicks the "publish" button.
Issues
Questions