AdvancedCustomFields / acf

Advanced Custom Fields
http://advancedcustomfields.com/
833 stars 169 forks source link

Action render_block_preview not firing on switching between edit and preview #396

Open mcdonagg opened 3 years ago

mcdonagg commented 3 years ago

The action render_block_preview is not firing when I switch between edit and preview mode if I have not changed anything. It works perfectly if changes were made.

elliotcondon commented 3 years ago

Hi @mcdonagg

Currently, the "render_block_preview" action will only be triggered when the block HTML is updated. This occurs when a field value, mode, block id or block className is changed.

In theory, this should be the only time JS needs to be actioned. Can you tell me a little more about the problem you are trying to solve, and how the "mode change" event would help solve this?

Darkvampir commented 3 years ago

Hi @mcdonagg, do you have jsx-support in your acf_register_block_type function enabled? Got the same problem here. If you don't need InnerBlocks set the jsx-support to false. This will fix the issue. Altering the dom via js and having jsx set to true arise lot's of issues.

@elliotcondon Here's how I reproduced the issue: For example you want to append a div in your js-file, while having jsx set to true. After switching between edit and preview mode (no changes made to any fields) the div disappears.

(function($){
    var initBlock = function( $block ) {
      // appending div   
      $block.find('.img-wrap').append( $('<div>append a div</div>') );
    };
    // frontend
    $(document).ready(function(){
        $('.section').each(function(){
            initBlock( $(this) );
        });
    });

    // backend preview
    if( window.acf ) {
          window.acf.addAction( 'render_block_preview', initBlock ); 
    }
})(jQuery);
elliotcondon commented 3 years ago

Thanks @Darkvampir - this helps clarify the issue.

Modifying the DOM in this way would be considered "against the grain" of how React and JSX work, which leaves me hesitant to consider this a bug that requires fixing.

Let me consider this, but for now, please use a "non JSX" enabled block type for any blocks that require DOM manipulation in this way.

aaronredwood commented 3 years ago

@elliotcondon I would like to bump this issue back onto your radar. My use case is, admittedly, a little bizarre.

On the front-end, we're using Vue, not react. So in the Editor, we need some of our blocks to use Vue to render. So on the final, rendered page, a Vue instance wraps the entire page, and any Vue components in the page are detected and rendered. In the Editor, we have to watch all the blocks individually. This works fine:

window.acf.addAction('render_block_preview/type=access-video', ($block) => {
  new Vue({
    components: {
      VideoPlayerWithTitle,
    },
  }).$mount($block[0])
})

Where it stops working is anytime the user hits the Editor button but doesn't make any changes before switching back to the Preview mode. When this happens, some layer (probably the block editor core) reloads the previously rendered HTML. Without a hook for this change event, there's nothing for me to use to trigger my Vue initialization again.

elliotcondon commented 3 years ago

Thanks @aaronredwood 👋. Wow, what an interesting mash up of technologies!

Until we find an elegant solution in core, I wonder if you could make use of the "remount" action. This action is run each time a DOM element is re-mounted, which will allow you to hook in when the block preview element is displayed after switching views. The only downside to this action is that it isn't block specific. You will need to review the $el parameter to check that the DOM element in question is your block. Other than that, it should work as a neat solution :)

window.acf.addAction('remount', ($el) => {
    // The $el DOM element has been remounted.
})

💪 There is also a sister "unmount" action if you happen to need that in the future.

aaronredwood commented 3 years ago

Worked perfectly, @elliotcondon, thank you so much! All I had to do was add a bit of markup to the output of the block template to indicate the block type, e.g., data-type-acf="access-video".

const bootstrapAcfVueBlock = (type, bootstrap) => {
  window.acf.addAction(`render_block_preview/type=${type}`, ($block, r) => {
    bootstrap($block, r).$mount($block[0])
  })

  window.acf.addAction('remount', ($content) => {
    $content.find(`[data-acf-type="${type}"]`).map(
      () => bootstrap($content).$mount($content[0])
    )
  })
}

bootstrapAcfVueBlock('access-video', () => {
  return new Vue({
    components: {
      VideoPlayerWithTitle,
    },
    mounted() {
      //
    },
  })
})

I'll probably expand on this to use that unmount action you mentioned to properly destroy the Vue component when the markup is unmounted.