<jupyter_start><jupyter_text>Asynchronous RPC with Vue ComponentsIn this example we create an HTML5 `` component on the client, pause and unpause it from the server and query some of its properties.You could replace `` with any other Vue component or HTML tag and then call its methods from the server. If a Vue component you would like to use is not available for ipyvue directly, you might want to have a look at [ipyvue-remote-component](https://github.com/saraedum/ipyvue-remote-component) or at using ``, see below.<jupyter_code>from ipyvue import VueTemplate
from traitlets import Unicode
from ipyvue_async import CommWidget
class Video(VueTemplate, CommWidget):
    r"""
    Shows 'Elephants Dream' by Orange Open Movie Project Studio, licensed under CC-3.0.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
      <video ref="video" width="512" poster="https://upload.wikimedia.org/wikipedia/commons/e/e8/Elephants_Dream_s5_both.jpg" >
        <source src="https://archive.org/download/ElephantsDream/ed_1024_512kb.mp4" type="video/mp4">
        <source src="https://archive.org/download/ElephantsDream/ed_hd.ogv" type="video/ogg">
        <source src="https://archive.org/download/ElephantsDream/ed_hd.avi" type="video/avi">
        Your browser doesn't support HTML 5 video.
      </video>
    </comm>
    """).tag(sync=True)
    
    async def play(self):
        r"""
        Start video playback.
        
        Calls `play` without parameters on the actual video component but does not report its
        return value or any errors.
        """
        return await self.poll(self.call("video", "play"))
        
    async def pause(self):
        r"""
        Pause video playback.
        
        Calls `pause` witout parameters on the actual video component but does not report its
        return value or any errors.      
        """
        return await self.poll(self.call("video", "pause"))
        
    async def paused(self):
        r"""
        Determine whether the videos are currently paused.
        
        Queries the "paused" attribute of the <video> elements.

        Note that the returned value can safely be awaited despite https://github.com/ipython/ipython/issues/12786
        since we are using https://github.com/Kirill888/jupyter-ui-poll to work around limitations
        in Jupyter's Python kernel.
        """
        return await self.poll(self.query("video", "paused"))<jupyter_output><empty_output><jupyter_text>We show the same video twice:<jupyter_code>video = Video()

# optional: shows the underlying communication on the console where this notebook was started.
# video.log.setLevel('DEBUG')

video
video<jupyter_output><empty_output><jupyter_text>Start video playback by calling `.play()` on the `` element.<jupyter_code>await video.play()<jupyter_output><empty_output><jupyter_text>Pause the video again by calling `.pause()` on the `` elements.<jupyter_code>await video.pause()<jupyter_output><empty_output><jupyter_text>Query the state of the video asynchronously:<jupyter_code>await video.paused()<jupyter_output><empty_output><jupyter_text>Invoking Methods on an Externally Hosted Vue ComponentUsing ipyvue-remote-component, we can pull in components that have not been prepared for use ipyvue in any way.<jupyter_code>from ipyvue import VueTemplate
from traitlets import Unicode
from ipyvue_async import CommWidget
from ipyvue_remote_component import RemoteComponent

class Plot(VueTemplate, RemoteComponent, CommWidget):
    r"""
    Display a plot with plotly.
    
    Note that in this particular example, it is much easier to just use traitlets to synchronize the x and y
    coordinates, see https://github.com/saraedum/ipyvue-remote-component/blob/master/examples/DynamicPlot.ipynb.
    The downside of using a traitlet is that it is less efficient since all the points need to be transferred 
    to the client with every update.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
        <component ref="plot" :is="{
            template: `
                <remote-component
                    url='https://unpkg.com/vue-plotly@^1/dist/vue-plotly.umd.min.js'
                    integrity='<KEY>wGpYg'
                    :extract='library => library.Plotly'
                    :data='[{ x: [...x], y: [...y], type: &quot;scatter&quot; }]'
                />
            `,
            data: function(){
                return {
                    x: [],
                    y: [],
                };
            },
            methods: {
                push: function(x, y) {
                    this.x.push(x);
                    this.y.push(y);
                },
            },
        }" />
    </comm>""").tag(sync=True)
    
    async def push(self, x, y):
        r"""
        Add a point with given (x, y) coordinates to the scatter plot.
        """
        return await self.poll(self.call("plot", "push", x, y))
    
plot = Plot()
plot
await plot.push(0, 1)
await plot.push(1, 3)
await plot.push(2, 3)
await plot.push(3, 7)<jupyter_output><empty_output><jupyter_text>Invoking Async Methods on a ComponentIn this example, the component on the frontend has a method `wait()` that returns a promise. Invoking this method with `query()` awaits the result of the promise and reports it back to Python.<jupyter_code>from ipyvue import VueTemplate
from traitlets import Unicode
from ipyvue_async import CommWidget

class WaitForClick(VueTemplate, CommWidget):
    r"""
    Displays a button and waits for the user to click on it.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
        <component ref="clickable" :is="{
            template: `<button @click='click'>{{ state }}</div>`,
            data: function() {
                return {
                    resolve: null
                };
            },
            computed: {
                state: function() {
                    return this.resolve ? 'Waiting for Click.' : 'Ready.';
                }
            },
            methods: {
                wait: async function() {
                    return await new Promise((resolve) => {
                        this.resolve = resolve;
                    })
                },
                click: function() {
                    if (this.resolve)
                        this.resolve('Ok.')
                    this.resolve = null;
                }
            }
        }" />
    </comm>
    """).tag(sync=True)
    
    async def wait(self):
        r"""
        Call the component's `wait` method and await the "Ok." it returns.
        """
        import asyncio
        return await self.poll(self.query("clickable", "wait", return_when=asyncio.FIRST_COMPLETED))
    
waiter = WaitForClick()
waiter
waiter
await waiter.wait()<jupyter_output><empty_output><jupyter_text>Using Cancellation TokensThere is a small issue in the above component. Since the same widget is shown twice, we only want the user to click the button in either of the two. But currently, one button is still waiting to be pressed even though nobody is actually waiting for that click anymore. We can of course explicitly ask all the frontend instances to cancel the request:<jupyter_code>from ipyvue import VueTemplate
from traitlets import Unicode
from ipyvue_async import CommWidget

class WaitForClick(VueTemplate, CommWidget):
    r"""
    Displays a button and waits for the user to click on it.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
        <component ref="clickable" :is="{
            template: `<button @click='click'>{{ state }}</div>`,
            data: function() {
                return {
                    resolve: null
                };
            },
            computed: {
                state: function() {
                    return this.resolve ? 'Waiting for Click.' : 'Ready.';
                }
            },
            methods: {
                wait: async function() {
                    return await new Promise((resolve) => {
                        this.resolve = resolve;
                    })
                },
                cancel: function() {
                    this.resolve = null;
                },
                click: function() {
                    if (this.resolve)
                        this.resolve('Ok.')
                    this.resolve = null;
                }
            }
        }" />
    </comm>
    """).tag(sync=True)
    
    async def wait(self):
        r"""
        Call the component's `wait` method and await the "Ok." it returns.
        """
        import asyncio
        value = await self.poll(self.query("clickable", "wait", return_when=asyncio.FIRST_COMPLETED))
        await self.poll(self.call("clickable", "cancel"))
        return value
    
waiter = WaitForClick()
waiter
waiter
await waiter.wait()<jupyter_output><empty_output><jupyter_text>However, since this is a very common issue, such cancellation can also be handled directly on the client by providing a `cancel()` method on the returned promise:<jupyter_code>class WaitForClick(VueTemplate, CommWidget):
    r"""
    Displays a button and waits for the user to click on it.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
        <component ref="clickable" :is="{
            template: `<button @click='click'>{{ state }}</div>`,
            data: function() {
                return {
                    resolve: null
                };
            },
            computed: {
                state: function() {
                    return this.resolve ? 'Waiting for Click.' : 'Ready.';
                }
            },
            methods: {
                wait: function() {
                    const token = {
                        cancelled: false,
                    };
                    const promise = this.waitAsync(token);
                    promise.cancel = () => {
                        token.cancelled = true;
                        this.cancel();
                    };
                    return promise;
                },
                waitAsync: async function() {
                    return await new Promise((resolve) => {
                        this.resolve = resolve;
                    })
                },
                cancel: function() {
                    this.resolve = null;
                },
                click: function() {
                    if (this.resolve)
                        this.resolve('Ok.')
                    this.resolve = null;
                }
            }
        }" />
    </comm>
    """).tag(sync=True)
    
    async def wait(self):
        r"""
        Call the component's `wait` method and await the "Ok." it returns.
        """
        import asyncio
        return await self.poll(self.query("clickable", "wait", return_when=asyncio.FIRST_COMPLETED))
    
waiter = WaitForClick()
waiter
waiter
await waiter.wait()<jupyter_output><empty_output><jupyter_text>Error HandlingJavaScript errors are displayed by the Widget:<jupyter_code>from ipyvue import VueTemplate
from traitlets import Unicode
from ipyvue_async import CommWidget

class HelloWorld(VueTemplate, CommWidget):
    r"""
    Displays a message.
    """
    template = Unicode(r"""
    <comm :refs="$refs">
        <component ref="hello" :is="{
            template: `<div>Hello {{ name }}!</div>`,
            data: function() {
                return {
                    name: 'noname',
                };
            },
            methods: {
                greet: function(name) {
                    if (typeof name !== 'string')
                        throw Error(`name must be a string but was ${name}`)
                    this.name = name;
                    return name;
                },
                greetAsync: async function(name) {
                    await new Promise((resolve) => setTimeout(resolve, 1000));
                    return this.greet(name);
                },
            }
        }" />
    </comm>
    """).tag(sync=True)
    
hello = HelloWorld()
hello
hello
await hello.poll(hello.call("hello", "greet"))<jupyter_output><empty_output><jupyter_text>Extra parameters are ignored by JavaScript, so this is not an error:<jupyter_code>await hello.poll(hello.call("hello", "greet", "firstname", "lastname"))<jupyter_output><empty_output><jupyter_text>Missing functions are reported:<jupyter_code>await hello.poll(hello.call("hello", "meet", "firstname", "lastname"))<jupyter_output><empty_output><jupyter_text>Exceptions thrown in promises are reported:<jupyter_code>await hello.poll(hello.call("hello", "greetAsync"))<jupyter_output><empty_output><jupyter_text>Exceptions thrown in queries are reported back to Python:<jupyter_code>await hello.poll(hello.query("hello", "greet"))
await hello.poll(hello.query("hello", "greetAsync"))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10867196014323525, 'fraction_numerical': 0.003035964502568893, 'mean_word_length': 2.006552773227241, 'pattern_counts': {'":': 0, '<': 90, '<?xml version=': 0, '>': 96, 'https://': 9, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '352493', 'license': 'Apache-2.0', 'n_tokens_mistral': 3617, 'n_tokens_neox': 3440, 'n_words': 1019, 'path': 'examples/rpc.ipynb', 'repo_name': 'flatsurf/ipyvue-async', 'size': '12907'}