Trying to add support for submit events

As I argue in this post, there’s a number of reasons why it would be good for Dash to support the submit event.

I’m trying to extend dash-html-components to support submit events. To begin with I just want to come up with a proof of concept to demonstrate that the various benefits I identify of using <form> tags combined with supporting the submit event actually work.

So far, in addition to adding submit as a valid DashEvent, I’ve also modified generateComponent in scripts/generate-components.js to have an onSubmit event like so (ideally this would only be added to <form> elements):

    const propTypes = generatePropTypes(element, attributes);

    return `                                                                                                                            
import React, {PropTypes} from 'react';                                                                                                 
                                                                                                                                        
const ${Component} = (props) => {                                                                                                       
    if (props.fireEvent || props.setProps) {                                                                                            
        return (                                                                                                                        
            <${element}                                                                                                                 
                onClick={() => {                                                                                                        
                    if (props.setProps) props.setProps({n_clicks: props.n_clicks + 1});                                                 
                    if (props.fireEvent) props.fireEvent({event: 'click'});                                                             
                }}                                                                                                                      
                onSubmit={(e) => {                                                                                                      
                    if (props.fireEvent) props.fireEvent({event: 'submit'});                                                            
                    e.preventDefault();                                                                                                 
                }}                                                                                                                      
                                                                                                                                        
                {...props}                                                                                                              
            >                                                                                                                           
                {props.children}                                                                                                        
            </${element}>                                                                                                               
        );                                                                                                                              
    } else {      
        return (                                                                                                                        
            <${element} {...props}>                                                                                                     
                {props.children}                                                                                                        
            </${element}>                                                                                                               
        );                                                                                                                              
    }                                                                                                                                   
};                                                                                                                                      
                                                                                                                                        
${Component}.defaultProps = {                                                                                                           
    n_clicks: 0                                                                                                                         
};                                                                                                                                      
                                                                                                                                        
${Component}.propTypes = {${propTypes}                                                                                                  
};                                                                                                                                      
                                                                                                                                        
export default ${Component};                                                                                                            
`;
}

The problem I’m running into is that within a test app that has a element and a callback listening for a submit event from the form, when I click on an input within the form, I get the following error from within dash-renderer’s bundle.js:

Uncaught Error: Node does not exist: form1.click
    at DepGraph.dependenciesOf (bundle.js?v=0.11.1:24194)
    at bundle.js?v=0.11.1:26733
    at bundle.js?v=0.11.1:12174
    at Object.fireEvent (bundle.js?v=0.11.1:28548)
    at onClick (bundle.js?v=0.8.0:8105)
    at Object.r (react-dom@15.4.2.min.js?v=0.11.1:14)
    at a (react-dom@15.4.2.min.js?v=0.11.1:12)
    at Object.s [as executeDispatchesInOrder] (react-dom@15.4.2.min.js?v=0.11.1:12)
    at f (react-dom@15.4.2.min.js?v=0.11.1:12)
    at m (react-dom@15.4.2.min.js?v=0.11.1:12)

If I’m interpreting things right, it looks like something thinks there’s a callback listening for a click event from the form. However I have no callbacks listening for click events, only for a submit event from the form.

After poking around through the call stack, I’m wondering if the problem might be that the click event is being dispatched to all observing elements (ie those that need to listen for an event from the originating element), and an assumption has been bade somewhere that the only thing one can observe is a click event, since up until now, that’s the only event – so it tries to find the corresponding click listener thing in the dependency graph, but there is none.

Would you mind sharing your test app?

Uncaught Error: Node does not exist: form1.click

This error is somehow derived from the fact that a component with the id form1 and property click is getting updated or has fired an event but it wasn’t initially supplied for some reason through the app.callback definitions.

Sure thing. The test app is below, and you can grab my modified dash-html-components here.

import dash                                    
from dash.dependencies import Input, State, Output, Event                                      
import dash_core_components as dcc             
import dash_html_components as html            

app = dash.Dash()                              
app.scripts.config.serve_locally = True        

app.layout = html.Div([                        
    html.Button('Foo', id='foo'),              
    html.Form(                                 
        id='form1',                            
        children=[                             
            dcc.Input(id='input1'),            
            html.Button(id='submit1', type='submit', children='Submit form 1')                 
        ]                                      
    ),                                         
    html.Form(                                 
        id='form2',                            
        children=[                             
            dcc.Input(id='input2'),            
            html.Button(id='submit2', type='submit', children='Submit form 2')                 
        ]                                      
    ),                                         
    html.Form(                                 
        id='form3',                            
        children=[                             
            dcc.Input(id='input3'),            
            html.Button(id='submit3', type='submit', children='Submit form 3')                 
        ]                                      
    ),                                         

    html.Div(id='target'),                     
])                                             


@app.callback(Output('target', 'children'),
              [],
              [State('input1', 'value'),       
               State('input2', 'value'),       
               State('input3', 'value')],      
              [Event('form1', 'submit'),       
               Event('form2', 'submit'),       
               Event('form3', 'submit')])      
def callback(state1, state2, state3):          
    return f"input1: {state1}, input2: {state2}, input3: {state3}"                             


if __name__ == '__main__':                     
    app.run_server(debug=True)  

any alternative way for event, please update me?