<p>Attaching an event listener for the iframe's <code>load</code> event is a very good way to solve this. The issue is how/when to attach that listener, as the <code>.lp-iframe</code> isn't attached to the DOM before the user actually enters Live Preview.</p>

<p>Craft exposes the current Live Preview instance (via the global <code>Craft</code> JavaScript object that is available inside the CP) and the Live Preview instance dispatches an <code>enter</code> event when the user enters Live Preview – at which point the <code>.lp-iframe</code> should be available in the DOM. This means that you need to listen for the <code>livePreview.enter</code> event, before attempting to attach the <code>load</code> event listener for the iframe.</p>

<p>The only trouble is actually getting to the <code>Craft.livePreview</code> instance, as your plugin's JavaScript will usually run before it is created. This means that if you try to read the <code>Craft.livePreview</code> attribute in your plugin's JavaScript on pageload, you'll just get an <code>undefined</code> value back.</p>

<p>One way to solve this, is to <em>poll</em> for the <code>Craft.livePreview</code> instance. Polling basically involves testing for the attribute continually, until its available (or after a reasonable amount of time has passed, and we can assume the attribute will <em>never</em> be available – at which point its a good idea to stop polling to avoid memory leaks etc).</p>

<p>Here's a script which will poll the <code>Craft</code> object for the <code>livePreview</code> instance for a full 2 seconds (should be more than enough!) using <code>requestAnimationFrame</code> (which basically means it'll check as often as 60 times each second). </p>

<p>If/when the <code>livePreview</code> instance is found, the script attaches event listeners to the <code>enter</code> and <code>exit</code> events. Inside the former event's listener (which will fire when Live Preview opens), one more event listener is attached for the <code>.lp-iframe</code> element's <code>load</code> event, so that finally, when the iframe loads, the <code>onLivePreviewIframeLoaded</code> event handler is called. Inside that last event handler, you should hopefully be able to accomplish what you need.</p>

<p>The following should be added to the <code>init</code> method in your plugin's primary class:</p>

<pre><code>// Only add the JS for non-AJAX CP requests
if (craft()-&gt;request-&gt;isCpRequest() &amp;&amp; !craft()-&gt;request-&gt;isAjaxRequest()) {

    craft()-&gt;templates-&gt;includeJs("

        function getLivePreview()
        {

            // Remove the RAF poller if it exists
            if (livePreviewPollerRaf) {
                Garnish.cancelAnimationFrame(livePreviewPollerRaf);
                livePreviewPollerRaf = null;
            }

            if (Craft.livePreview) {

                // I can haz live preview. Attach event listeners
                livePreview = Craft.livePreview;
                livePreview.on('enter', onLivePreviewEnter);
                livePreview.on('exit', onLivePreviewExit);

                console.log('Live preview is available');

            } else if (new Date().getTime() - now &lt; 2000) { // Only poll for 2 seconds

                livePreviewPollerRaf = Garnish.requestAnimationFrame(livePreviewPoller);

            }
        }

        function onLivePreviewEnter()
        {
            Garnish.requestAnimationFrame(function () {
                $('.lp-iframe').on('load', onLivePreviewIframeLoaded);
            });
        }

        function onLivePreviewExit()
        {
            // Remove the load handler, lets be clean
            $('.lp-iframe').off('load');
        }

        function onLivePreviewIframeLoaded(e)
        {
            console.log('live preview iframe loaded', e);
            // ** THIS IS WHERE YOU CAN DO STUFF **
        }

        // Start by polling for the live preview instance
        var livePreview = null;
        var livePreviewPollerRaf = null;
        var livePreviewPoller = getLivePreview;
        var now = new Date().getTime();

        livePreviewPoller();

    ");

}
</code></pre>

CC BY-SA 3.0
2016-06-09T23:46:43.027Z
Excellent! Thank you very much for the in-depth writeup! I should be able to give this a try tomorrow. I'm hoping I can find a better way than polling to check for the existence of the iframe, but I'll test this first and mark answered when it does the trick. Thanks again!
Gavin Anderegg
CC BY-SA 3.0
2016-06-09T23:50:08.813Z
No worries, @GavinAnderegg – hope it's helpful. I concur that polling isn't ideal, might be there's a better way, but I haven't found it. Something similar to the code above is actually in my [Reasons](https://github.com/mmikkel/Reasons-Craft) plugin – please let me know if you figure it out without the polling ;)
Mats Mikkel Rummelhoff
CC BY-SA 3.0
2016-06-10T14:39:03.807Z
This works really well. Thanks again for the help. I'll let you know if I figure out some way other than the polling. Thanks!
Gavin Anderegg
CC BY-SA 3.0
2016-06-11T06:02:04.833Z
Awesome! I guess you could drop the polling and attach the `Craft.livePreview.enter` event handler when the Live Preview button is clicked (as per @spAnser's answer). Not sure if I'd consider that a better solution per se, but its an option :)
Mats Mikkel Rummelhoff
CC BY-SA 3.0
2016-06-09T23:09:24.263Z
2016-06-09T23:16:06.057Z
2016-06-09T23:16:06.057Z
Mats Mikkel Rummelhoff
<p>You will probably have to use javascript. Something like this would work.</p>

<pre><code>$('.lp-iframe').on('load', function(event) {
  console.log(event);
});
</code></pre>

<p><em>edit</em></p>

<p>Try this to bind the lp-iframe after it gets added to the page. </p>

<pre><code>$('.livepreviewbtn').on('click', bindIframe);

function bindIframe() {
  $('.livepreviewbtn').off('click', bindIframe);
  $('.lp-iframe').on('load', function(event) {
    console.log(event);
  });
}
</code></pre>

CC BY-SA 3.0
2016-06-09T20:17:29.390Z
I tried injecting this with craft()->templates->includeJs(), but nothing additional gets logged.
Gavin Anderegg
CC BY-SA 3.0
2016-06-09T21:51:30.353Z
Looks like the lp-iframe is no available on the page until it is called.
spAnser
CC BY-SA 3.0
2016-06-09T20:00:03.370Z
2016-06-09T22:06:03.450Z
2016-06-09T22:06:03.450Z
spAnser
<p>I'm working on a site that involves taking a URL and then getting information about that URL using an API. The URL is entered into a field in a Matrix block, and then another field in the same Matrix block is populated automatically on save. This is working, but only when the article is saved. I'd also like it to work in live preview mode. The event I'm using on save is <code>entries.saveEntry</code>, but I've also tried using <code>entryRevisions.onSaveDraft</code>, hoping that drafts were saved with live preview changes, but that didn't work.</p>

<p><strong>Question:</strong>
Is there some event I can listen for that lets me know when the live preview is about to refresh? Can this be done with JavaScript in the admin instead? If so, is there documentation that describes how to do this?</p>

<plugin-development><events><live-preview>
Is there a way a plugin can watch for live preview updates?
# Is there a way a plugin can watch for live preview updates?

(Asked by: username_0 on 2016-06-09)

I'm working on a site that involves taking a URL and then getting information about that URL using an API. The URL is entered into a field in a Matrix block, and then another field in the same Matrix block is populated automatically on save. This is working, but only when the article is saved. I'd also like it to work in live preview mode. The event I'm using on save is `entries.saveEntry`, but I've also tried using `entryRevisions.onSaveDraft`, hoping that drafts were saved with live preview changes, but that didn't work.

**Question:**
Is there some event I can listen for that lets me know when the live preview is about to refresh? Can this be done with JavaScript in the admin instead? If so, is there documentation that describes how to do this?

**Tags:** plugin-development, events, live-preview

---

## 2 answers

### Answer by username_1 on 2016-06-09. Score: 2 | Accepted answer

Attaching an event listener for the iframe's load event is a very good way to solve this. The issue is how/when to attach that listener, as the .lp-iframe isn't attached to the DOM before the user actually enters Live Preview.
Craft exposes the current Live Preview instance (via the global Craft JavaScript object that is available inside the CP) and the Live Preview instance dispatches an enter event when the user enters Live Preview – at which point the .lp-iframe should be available in the DOM. This means that you need to listen for the livePreview.enter event, before attempting to attach the load event listener for the iframe.
The only trouble is actually getting to the Craft.livePreview instance, as your plugin's JavaScript will usually run before it is created. This means that if you try to read the Craft.livePreview attribute in your plugin's JavaScript on pageload, you'll just get an undefined value back.
One way to solve this, is to poll for the Craft.livePreview instance. Polling basically involves testing for the attribute continually, until its available (or after a reasonable amount of time has passed, and we can assume the attribute will never be available – at which point its a good idea to stop polling to avoid memory leaks etc).
Here's a script which will poll the Craft object for the livePreview instance for a full 2 seconds (should be more than enough!) using requestAnimationFrame (which basically means it'll check as often as 60 times each second). 
If/when the livePreview instance is found, the script attaches event listeners to the enter and exit events. Inside the former event's listener (which will fire when Live Preview opens), one more event listener is attached for the .lp-iframe element's load event, so that finally, when the iframe loads, the onLivePreviewIframeLoaded event handler is called. Inside that last event handler, you should hopefully be able to accomplish what you need.
The following should be added to the init method in your plugin's primary class:
// Only add the JS for non-AJAX CP requests
if (craft()->request->isCpRequest() && !craft()->request->isAjaxRequest()) {

    craft()->templates->includeJs("

        function getLivePreview()
        {

            // Remove the RAF poller if it exists
            if (livePreviewPollerRaf) {
                Garnish.cancelAnimationFrame(livePreviewPollerRaf);
                livePreviewPollerRaf = null;
            }

            if (Craft.livePreview) {

                // I can haz live preview. Attach event listeners
                livePreview = Craft.livePreview;
                livePreview.on('enter', onLivePreviewEnter);
                livePreview.on('exit', onLivePreviewExit);

                console.log('Live preview is available');

            } else if (new Date().getTime() - now < 2000) { // Only poll for 2 seconds

                livePreviewPollerRaf = Garnish.requestAnimationFrame(livePreviewPoller);

            }
        }

        function onLivePreviewEnter()
        {
            Garnish.requestAnimationFrame(function () {
                $('.lp-iframe').on('load', onLivePreviewIframeLoaded);
            });
        }

        function onLivePreviewExit()
        {
            // Remove the load handler, lets be clean
            $('.lp-iframe').off('load');
        }

        function onLivePreviewIframeLoaded(e)
        {
            console.log('live preview iframe loaded', e);
            // ** THIS IS WHERE YOU CAN DO STUFF **
        }

        // Start by polling for the live preview instance
        var livePreview = null;
        var livePreviewPollerRaf = null;
        var livePreviewPoller = getLivePreview;
        var now = new Date().getTime();

        livePreviewPoller();

    ");

}

> **Comments:**
> **username_0**: Excellent! Thank you very much for the in-depth writeup! I should be able to give this a try tomorrow. I'm hoping I can find a better way than polling to check for the existence of the iframe, but I'll test this first and mark answered when it does the trick. Thanks again!  
> **username_1**: No worries, @GavinAnderegg – hope it's helpful. I concur that polling isn't ideal, might be there's a better way, but I haven't found it. Something similar to the code above is actually in my [Reasons](https://github.com/mmikkel/Reasons-Craft) plugin – please let me know if you figure it out without the polling ;)  
> **username_0**: This works really well. Thanks again for the help. I'll let you know if I figure out some way other than the polling. Thanks!  
> **username_1**: Awesome! I guess you could drop the polling and attach the `Craft.livePreview.enter` event handler when the Live Preview button is clicked (as per @spAnser's answer). Not sure if I'd consider that a better solution per se, but its an option :)  

---

### Answer by username_2 on 2016-06-09. Score: 2

You will probably have to use javascript. Something like this would work.
$('.lp-iframe').on('load', function(event) {
  console.log(event);
});

edit
Try this to bind the lp-iframe after it gets added to the page. 
$('.livepreviewbtn').on('click', bindIframe);

function bindIframe() {
  $('.livepreviewbtn').off('click', bindIframe);
  $('.lp-iframe').on('load', function(event) {
    console.log(event);
  });
}

> **Comments:**
> **username_0**: I tried injecting this with craft()->templates->includeJs(), but nothing additional gets logged.  
> **username_2**: Looks like the lp-iframe is no available on the page until it is called.  

---

