16

So I have a metabox which I want to trigger some Javascript when a post is saved (to refresh the page in this use case.)

In Classic Editor, this can be done via a simple redirect hooked to save_post (with a high priority)

But since Gutenberg converts the saving process for existing metaboxes into individual AJAX calls now, it needs to be javascript, so how do I either:

  • Listen for an event where all the saving processes are complete and then trigger the javascript? If so what is this event called? Is there a reference to these events anywhere yet? OR

  • Trigger javascript inside the metabox saving AJAX process, which can then check the state of the parent page saving process before continuing?

7
  • 1
    Making Gutenberg reload just your metabox is also a potential solution, as would implementing the metabox UI in JS and relying on wp.data data stores
    – Tom J Nowell
    Commented Nov 13, 2018 at 2:07
  • @TomJNowell I have found this reference I can use to check some states, but not sure how to access them: wordpress.org/gutenberg/handbook/data/data-core-editor
    – majick
    Commented Nov 13, 2018 at 2:37
  • 1
    so far I have: eg. wp.data.select('core/editor').isSavingPost() ... this kind of access is not documented anywhere I can see... and it seems also unreliable as it returns false after the post is first saved (before that it is undefined) whether the editor is still saving or not. facepalm
    – majick
    Commented Nov 13, 2018 at 5:50
  • You can raise an issue on the gutenberg repo for support too, it's on topic here but you might get more knowledgable people answering there. Also the JS WP hooks system might be a possibility but that's just a guess
    – Tom J Nowell
    Commented Nov 13, 2018 at 7:02
  • Crazily, something simple like this is already asked for and not supported: github.com/WordPress/gutenberg/issues/10044 ... hence I am trying to find a way to do it myself.
    – majick
    Commented Nov 13, 2018 at 7:14

6 Answers 6

16

Not sure if there is a better way, but I am listening to subscribe rather than adding an event listener to the button:

wp.data.subscribe(function () {
  var isSavingPost = wp.data.select('core/editor').isSavingPost();
  var isAutosavingPost = wp.data.select('core/editor').isAutosavingPost();

  if (isSavingPost && !isAutosavingPost) {
    // Here goes your AJAX code ......

  }
})

Official docs of the Post Editor data: https://wordpress.org/gutenberg/handbook/designers-developers/developers/data/data-core-editor/

6
  • this looks much cleaner, just curious where does the subscribe method even come from? is it part of the wp.data function? I don't see it mentioned in the docs.
    – majick
    Commented Mar 12, 2019 at 13:50
  • Yes, subscribe is a method of the wp.data module. Open the console when editing a post with Gutenberg and run wp.data. This lists all available data module methods.
    – tomyam
    Commented Mar 13, 2019 at 8:37
  • 4
    well done on finding this! it's unfortunate the gutenberg docs are so obscurely laid out and don't have enough examples. plus the expectation that developers will know and/or want to learn React methods is really too much... I'm sure it can be quite a timesaver if you know it already, but it's a real timewaster if you don't - took me hours just to work out how to access anything useful in wp.data model. it's back to PHP (and classic editor) for me.
    – majick
    Commented Mar 14, 2019 at 9:30
  • Thanks for sharing this! How can I intercept and stop updating/publishing the post based on a condition. Commented Dec 12, 2019 at 1:27
  • 1
    It appears this method also triggers the code when a user clicks "Move to Trash" button (the post status changes to "trash" and isSavingPost value is "true" regardless of this). Also, one "Update" click triggered the subscription code 3 times in my case. I ended up listening for click on .editor-post-publish-button, .editor-post-save-draft and .editor-post-preview. Commented Mar 6, 2020 at 13:57
4

Okay, so way way more hacky solution than I wanted, but got it working...

Here is a slightly simplified and abstracted way of doing it from my code, in case anyone ever needs to do the same (as I'm sure more plugins will in the near future.)

    var reload_check = false; var publish_button_click = false;
    jQuery(document).ready(function($) {
        add_publish_button_click = setInterval(function() {
            $publish_button = jQuery('.edit-post-header__settings .editor-post-publish-button');
            if ($publish_button && !publish_button_click) {
                publish_button_click = true;
                $publish_button.on('click', function() {
                    var reloader = setInterval(function() {
                        if (reload_check) {return;} else {reload_check = true;}
                        postsaving = wp.data.select('core/editor').isSavingPost();
                        autosaving = wp.data.select('core/editor').isAutosavingPost();
                        success = wp.data.select('core/editor').didPostSaveRequestSucceed();
                        console.log('Saving: '+postsaving+' - Autosaving: '+autosaving+' - Success: '+success);
                        if (postsaving || autosaving || !success) {classic_reload_check = false; return;}
                        clearInterval(reloader);

                        value = document.getElementById('metabox_input_id').value;
                        if (value == 'trigger_value') {
                            if (confirm('Page reload required. Refresh the page now?')) {
                                window.location.href = window.location.href+'&refreshed=1';
                            }
                        }
                    }, 1000);
                });
            }
        }, 500);
    });

...just need to change metabox_input_id and trigger_value to match as needed. :-)

1
  • 1
    This was useful, the only reference example I could find to accessing the gutenberg javascript module hierarchy: github.com/front/gutenberg-js
    – majick
    Commented Nov 13, 2018 at 8:18
2

In order to trigger the action (in this case an Ajax request) AFTER the post save is COMPLETE, you can use an interval to wait until the isSavingPost returns false again.

let intervalCheckPostIsSaved;
let ajaxRequest;

wp.data.subscribe(function () {
    let editor = wp.data.select('core/editor');

    if (editor.isSavingPost()
         && !editor.isAutosavingPost()
         && editor.didPostSaveRequestSucceed()) {

        if (!intervalCheckPostIsSaved) {
            intervalCheckPostIsSaved = setInterval(function () {
                if (!wp.data.select('core/editor').isSavingPost()) {
                    if (ajaxRequest) {
                        ajaxRequest.abort();
                    }

                    ajaxRequest = $.ajax({
                        url: ajaxurl,
                        type: 'POST',
                        data: {},
                        success: function (data) {
                            ajaxRequest = null;
                        }
                    });

                    clearInterval(intervalCheckPostIsSaved);
                    intervalCheckPostIsSaved = null;
                }
            }, 800);
        }
    }
});
1
  • Def the way to go - works so well. Great job! Commented Dec 6, 2021 at 17:27
2

If someone is still interested I came up with a simple way to actually execute something right AFTER the block editor (Gutenberg) finishes a post publish/update:

const editor = window.wp.data.dispatch('core/editor')
const savePost = editor.savePost

editor.savePost = function (options) {
    options = options || {}

    return savePost(options)
        .then(() => {
            // Do something after the post was actually asynchronous saved.
            console.log('The post was saved.')

            if (!options.isAutosave) {
               // This is not an autosave.
            }
        })
}

Basically the snippet above overrides the native savePost() function.
So you override it with your own function, call savePost() again inside it and take advantage of the promise returned just by using then.

1

You need collect unsubscribe function from subscribe and call to avoid multiples time call.

const unsubscribe = wp.data.subscribe(function () {
            let select = wp.data.select('core/editor');
            var isSavingPost = select.isSavingPost();
            var isAutosavingPost = select.isAutosavingPost();
            var didPostSaveRequestSucceed = select.didPostSaveRequestSucceed();
            if (isSavingPost && !isAutosavingPost && didPostSaveRequestSucceed) {
                console.log("isSavingPost && !isAutosavingPost && didPostSaveRequestSucceed");
                unsubscribe();


                // your AJAX HERE();

            }
        });
2
  • 1
    This seems to work fine, except that it still triggers BEFORE the complete post save. My code placed underneath "unsubscribe() fires before that WP launches the post save by AJAX. Is there a solution to run code after WP has 100% finish with post save/update ?
    – Neoweiter
    Commented Jun 1, 2020 at 19:21
  • I can confirm it triggers before the post finishes saving. I added a new answer that I think could work to trigger only after the post finished saving: wordpress.stackexchange.com/a/390543/171971 Commented Jun 15, 2021 at 0:34
0

I wrote a blog post about this - https://thewpvoyage.com/how-to-detect-when-a-post-is-done-saving-in-wordpress-gutenberg/

You can use the following hook:

import { useBlockProps } from '@wordpress/block-editor';
import { useRef, useState, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
 * Returns `true` if the post is done saving, `false` otherwise.
 *
 * @returns {Boolean}
 */
const useAfterSave = () => {
    const [ isPostSaved, setIsPostSaved ] = useState( false );
    const isPostSavingInProgress = useRef( false );
    const { isSavingPost, isAutosavingPost } = useSelect( ( __select ) => {
        return {
            isSavingPost: __select( 'core/editor' ).isSavingPost(),
            isAutosavingPost: __select( 'core/editor' ).isAutosavingPost(),
        }
    } );

    useEffect( () => {
        if ( ( isSavingPost || isAutosavingPost ) && ! isPostSavingInProgress.current ) {
            setIsPostSaved( false );
            isPostSavingInProgress.current = true;
        }
        if ( ! ( isSavingPost || isAutosavingPost ) && isPostSavingInProgress.current ) {
            // Code to run after post is done saving.
            setIsPostSaved( true );
            isPostSavingInProgress.current = false;
        }
    }, [ isSavingPost, isAutosavingPost ] );

    return isPostSaved;
};

/**
 * The edit function of an example block.
 *
 * @return {WPElement} Element to render.
 */
export default function Edit() {
    const isAfterSave = useAfterSave();

    useEffect( () => {
        if ( isAfterSave ) {
            // Add your code here that must run after the post is saved.
            console.log( '...done saving...' )
        }
    }, [ isAfterSave ] );

    return (
        <p { ...useBlockProps() }>
            { __( 'Todo List – hello from the editor!', 'todo-list' ) }
        </p>
    );
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.