Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sending input event via javascript to text area produces error messages in browser's console #2981

Closed
1 task done
AUTOMATIC1111 opened this issue Jan 14, 2023 · 7 comments
Labels
bug Something isn't working

Comments

@AUTOMATIC1111
Copy link

Describe the bug

The only way we've found so far to edit a gradio textarea from javascript is to create an "input" event.

The code below demonstrates this: left button just sets textarea content from javascript, right button sets content and creates an event, and bottom button copies textarea contents to label via gradio.

If you press left and then bottom button, the text "abc" is not copied to label. If you press right and then bottom button, the text "def" is copied to label. So the event is necessary.

The problem is whenever the event is sent I get a very unpleasant looking exception stack trace in browser. Since everything is obfuscated, I don't want to fix it myself client-size since then I'd have to exit the fix every time I update gradio.

Is there an existing issue for this?

  • I have searched the existing issues

Reproduction

import gradio as gr

with gr.Blocks(analytics_enabled=False) as demo:
    text = gr.TextArea(label="Copy from", elem_id="elem")
    label = gr.Label(label="Copy to")
    with gr.Row():
       set_text_without = gr.Button('Set text via Javascript without event')
       set_text_with = gr.Button('Set text via Javascript with event')
    go = gr.Button('Copy text from textarea into label', variant="primary")

    set_text_without.click(
        fn=lambda: None,
        _js="""
    function(){
        elem = document.getElementsByTagName('gradio-app')[0].shadowRoot.querySelector('#elem textarea')
        elem.value = "abc"
        return []
    }
            """,
        inputs=[],
        outputs=[],
    )

    set_text_with.click(
        fn=lambda: None,
        _js="""
    function(){
        elem = document.getElementsByTagName('gradio-app')[0].shadowRoot.querySelector('#elem textarea')
        elem.value = "def"
        elem.dispatchEvent(new Event("input", { bubbles: true }));
        return []
    }
            """,
        inputs=[],
        outputs=[],
    )

    go.click(
        fn=lambda x: x,
        inputs=[text],
        outputs=[label],
    )

demo.queue().launch(share=False)

Screenshot

No response

Logs

Uncaught (in promise) TypeError: R is null
    v index.0b923826.js:76
    result index.0b923826.js line 75 > AsyncFunction:7
    anonymous index.0b923826.js line 75 > AsyncFunction:10
    ac index.0b923826.js:34
    Nt index.0b923826.js:76
    ae index.0b923826.js:4
    ae index.0b923826.js:4
    _ Button.svelte:10
    ae index.0b923826.js:4
    ae index.0b923826.js:4
    B Button.svelte:9
    Z index.0b923826.js:1
    m Button.svelte:14
    ct index.0b923826.js:4
    m Button.svelte:14
    ct index.0b923826.js:4
    m index.0b923826.js:34
    ct index.0b923826.js:4
    m index.0b923826.js:34
    m index.0b923826.js:34
    m index.0b923826.js:34
    m Row.svelte:10
    ct index.0b923826.js:4
    m index.0b923826.js:34
    ct index.0b923826.js:4
    m index.0b923826.js:34
    m index.0b923826.js:34
    m index.0b923826.js:34
    m Column.svelte:13
    ct index.0b923826.js:4
    m index.0b923826.js:34
    ct index.0b923826.js:4
    m index.0b923826.js:74
    p index.0b923826.js:75
    Rl index.0b923826.js:4
    mt index.0b923826.js:4
    promise callback*so index.0b923826.js:4
    zl index.0b923826.js:4
    ctx index.0b923826.js:4
    xc index.0b923826.js:76
    promise callback*xc/< index.0b923826.js:76
    promise callback*xc index.0b923826.js:76
    Tt index.0b923826.js:4
    kc index.0b923826.js:76
    ml index.0b923826.js:77
    connectedCallback index.0b923826.js:77
    w0 index.0b923826.js:77
    <anonymous> index.0b923826.js:77
index.0b923826.js:76:20397


### System Info

```shell
gradio-3.15.0 (and same in gradio-3.16.1), Windows 10, python 3.10.6

Severity

annoying

@AUTOMATIC1111 AUTOMATIC1111 added the bug Something isn't working label Jan 14, 2023
@Gerschel
Copy link

Here's the fix. That function that throws the error is not seeing the target when the event is dispatched manually. So when you make your event, you'll need to add the target.

function(){
    let elemTextArea = gradioApp().querySelector(#txt2img_prompt textarea)
    // define event separately
    let myEvent = new Event("input")
    // update it's target to the element
    Object.defineProperty(myEvent, "target", {value: elemTextArea})
    // dispatch the event with a non null target
    elemTextArea.dispatchEvent(myEvent)
}

@AUTOMATIC1111
Copy link
Author

Ah. Thanks. That helps. I tried doing myEvent.target = elemTextArea and myEvent["target"] = elemTextArea myself and that didn't do the trick.

I guess than means this is not even gradio's fault and was just my code not creating events properly.

AUTOMATIC1111 added a commit to AUTOMATIC1111/stable-diffusion-webui that referenced this issue Jan 17, 2023
@abidlabs
Copy link
Member

Thanks @Gerschel and @AUTOMATIC1111!

lshqqytiger pushed a commit to lshqqytiger/stable-diffusion-webui-amdgpu that referenced this issue Jan 20, 2023
hgrsikghrd pushed a commit to hgrsikghrd/stable-diffusion-webui-directml that referenced this issue Jan 20, 2023
lshqqytiger pushed a commit to lshqqytiger/stable-diffusion-webui-amdgpu that referenced this issue Jan 21, 2023
hgrsikghrd pushed a commit to hgrsikghrd/stable-diffusion-webui-directml that referenced this issue Jan 21, 2023
lshqqytiger pushed a commit to lshqqytiger/stable-diffusion-webui-amdgpu that referenced this issue Jan 22, 2023
lshqqytiger pushed a commit to lshqqytiger/stable-diffusion-webui-amdgpu that referenced this issue Jan 22, 2023
lshqqytiger pushed a commit to hgrsikghrd/stable-diffusion-webui-directml that referenced this issue Jan 22, 2023
DominikDoom added a commit to DominikDoom/a1111-sd-webui-tagcomplete that referenced this issue Feb 21, 2023
This also solves the annoying console error after completion, which was due to missing the target property
See gradio-app/gradio#2981 for details
@ysig
Copy link

ysig commented Apr 15, 2024

@abidlabs Is there an equivalent of:

function(){
    let elemTextArea = gradioApp().querySelector(#txt2img_prompt textarea)
    // define event separately
    let myEvent = new Event("input")
    // update it's target to the element
    Object.defineProperty(myEvent, "target", {value: elemTextArea})
    // dispatch the event with a non null target
    elemTextArea.dispatchEvent(myEvent)
}

in gradio 4? Both gradioApp() and document.getElementsByTagName('gradio-app')[0].shadowRoot.querySelector('#elem textarea') don't work and I can't find any ref in #6339.

@pngwn
Copy link
Member

pngwn commented Apr 16, 2024

What specifically are you trying to do with this code snippet?

@ysig
Copy link

ysig commented Apr 16, 2024

@pngwn I'm trying to put it in a callback so that a javascript component (folium in this case) can write information onto the gradioverse, that I can then access in Gradio.

@CailenR
Copy link

CailenR commented May 8, 2024

@pngwn I'm trying to put it in a callback so that a javascript component (folium in this case) can write information onto the gradioverse, that I can then access in Gradio.

I was running into the same issue with the folium component and found a way that might help you. In my use case I wanted to populate the lat and lng of a newly placed marker as a gradio input. I solved this with two scripts, first, by making a modified folium ClickForMarker function that on the placement of the marker, modifies the parent component with a new attribute containing the coordinates.

class ClickForMarker(MacroElement):
   ....
  _template = Template(
        """
            {% macro script(this, kwargs) %}
                function newMarker(e){
                    var new_mark = L.marker().setLatLng(e.latlng).addTo({{this._parent.get_name()}});
                    new_mark.dragging.enable();
                    new_mark.on('dblclick', function(e){ {{this._parent.get_name()}}.removeLayer(e.target)})
                    var lat = e.latlng.lat.toFixed(4),
                       lng = e.latlng.lng.toFixed(4);

                    document.getElementById('{{this._parent.get_name()}}').setAttribute("coordinates",''+lat +',' +lng);

                    new_mark.bindPopup({{ this.popup }});
                    };
                {{this._parent.get_name()}}.on('click', newMarker);
            {% endmacro %}
            """
    )

Second I added a js function the Gradio Block that would wait for the map iframe to load and then add an event listener to watch for mutation of the folium component and pull the coordinates attributes and fill in my chosen gradio.Textboxes.

js = """
function prepareMap(){

        var iframe = document.getElementById("MAP").getElementsByTagName("iframe")[0]
        iframe.addEventListener("load", function(){
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.type === "attributes") {
                        if (mutation.target.getAttribute("coords") !== "undefined" && mutation.target.getAttribute("coords") !== null){
                            var latlng = mutation.target.getAttribute("coords")
                            console.log("attributes changed", latlng);
                            var latlngArray = latlng.split(",")
                            inputLat = document.querySelector("#input_Lat textarea")
                            inputLng = document.querySelector("#input_Lon textarea")
                            inputLat.value = (latlngArray[0])
                            inputLng.value = (latlngArray[1])
                            var event = new Event('input')
                            inputLat.dispatchEvent(event)
                            inputLng.dispatchEvent(event)
                        }
                    }
                });
            });
            map = document.getElementById("MAP").getElementsByTagName("iframe")[0].contentWindow.document.getElementsByClassName("folium-map")[0]
            console.log(map)    
            observer.observe(map,{attributes: true});
            
        })
"""
...
with gr.Blocks(css=css_path,js=js) as demo:
#setup of the gradio interface

Its is a bit hacky and probably not ideal but it works with getting coordinates out of folium iframe maps. This below code fragment,

...
 inputLat = document.querySelector("#input_Lat textarea")
 inputLat.value = (latlngArray[0])
 var event = new Event('input')
 inputLat.dispatchEvent(event)
...

was the secret sauce that I needed to get gradioverse to recognize the value of the updated gradio textbox. Other ways of updating that I tried would update visually but I found that gradio wouldn't acknowledge the updated values when trying to use them for processing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

6 participants