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

HTML5 templates? #44

Closed
zafarali opened this issue Jun 26, 2017 · 13 comments
Closed

HTML5 templates? #44

zafarali opened this issue Jun 26, 2017 · 13 comments

Comments

@zafarali
Copy link

Most of the examples I have seen rely on building the entire HTML front end via the python interface. Is there a way to use a pre-defined HTML5 template and substitute the graphs in there?

This way one can prototype the interface independently of the dash logic.

@cedricyau
Copy link

cedricyau commented Jun 26, 2017 via email

@chriddyp
Copy link
Member

There isn't any plan to do this right now. While the dash_html_components components render themselves directly as html components, the dash_core_components components can't be rendered with just HTML tags (unless we switched them to web components). This limits the extent to which a Dash app could be mocked up entirely in HTML.

Writing web markup with Python classes may seem pretty odd at first, but there are actually a lot of really great things that you can do it with it:

  • If you're using inline styles, you can just use Python variables. See https://speakerdeck.com/vjeux/react-css-in-js for a really compelling deck about this.
  • You can use list concatenation and python code to generate markup. This markup can be reused and imported in other Dash programs. For example, see the Table example in the getting started section. You could write a generic app.layout template in Dash and then re-use it in the rest of your projects or share it with your team members (since it's just Python code)
  • You can easily embed in real data into your prototypes. Tables made out of DataFrames, Dropdowns populated with the unique values of a DataFrame column or the column names, a graph with real data, etc.

This way one can prototype the interface independently of the dash logic.

The "logic" of a Dash app is separated from the initial "view" of the app already. The app's app.layout property defines what the app looks like, and the app.callbacks define the logic of the app, or how the app responds to changes.

@chriddyp
Copy link
Member

Closing this for now since it's not on the immediate roadmap, but certainly always happy to hear more thoughts or feedback :)

@chriddyp
Copy link
Member

Another note - one could also write a Dash component that takes a raw HTML string and renders it with React's dangerouslySetInnerHTML. It could look something like:

RawHTML('''
<table>
    [...]
</table>
''')

and then if you wanted Jinja templating, you could run your string through Jinja before passing into the component, like:

RawHTML(Jinja.render('''
<table>
    [...]
</table>
'''))

Here's how to create custom components in Dash: https://plot.ly/dash/plugins.

@nirvana-msu
Copy link

nirvana-msu commented Oct 13, 2017

I would argue its extremely useful to be able to generate HTML using other means than just dash_html_components. I'm personally not a fan of templates such as Jinja, but I do love the dominate library. Its syntax is concise and the use of with keyword makes your code much more elegant and readable than the current approach of dash_html_components. If I had to design something similar to Dash from scratch, I would certainly try to build on top of dominate (basically just extend it to allow plugging in dash_core_components and other things).

Apart from these aesthetic considerations, dash_html_components enforces some unreasonable restrictions on the allowed set of HTML element attributes, This does not make sense in a modern web front-end development where almost every fancy library requires you to set some custom HTML attributes. The only way I can get around it is by adding the attributes using javascript on the client side... I do not see the point in these restrictions. That's great that Dash allows you to do a lot with just Python, but I don't see why it should restrict you if you want to do more with 3rd party client-side libraries,

TL;DR I do think it should be possible to plug in HTML code generated elsewhere into Dash. Seamless support for dominate would be awesome, but even just the RawHTML suggested above is good enough because it'll get the job done.

@adrianoesch
Copy link
Contributor

one alternative might be to ditch dash for just flask and plotly (which are core components of dash anyway) and build on top of that. back in the days @chriddyp showed us how it's done here.

@chriddyp
Copy link
Member

chriddyp commented Dec 7, 2017

I ran into this myself on a personal project where I wanted to render HTML from a third-party source and converting it to the dash-html-components library would've been too complex.

I have created a DangerouslySetInnerHTML component that you can use now https://github.com/plotly/dash-dangerously-set-inner-html. However, I recommend using the dash-html-components as much as possible.

I still stand by the belief that dash-html-components is a great way to render complex HTML as you are writing code in Python. This automatically provides us with:

  • Flexible, powerful templating (since it's just Python)
  • Reusable, portable components (since you can encapsulate components as functions and import them)
  • Nice argument validation

For example, conditional formatting a table to create a component like
image
is trivial (see https://community.plot.ly/t/coloured-text-in-a-table-conditional-formatting/7112/8?u=chriddyp)

Further, you can easily re-use components that you have created since it's just regular Python!

dash_html_components enforces some unreasonable restrictions on the allowed set of HTML element attributes,

As far as I'm aware, this is just related to Dash not currently supporting data-* and aria-* attributes. This is more of a bug than a limitation, and it will be supported in the future.

@nirvana-msu
Copy link

nirvana-msu commented Jan 3, 2018

@chriddyp, if I understand you correctly, what you say about Reusable, portable components and Nice argument validation refers mostly to dash_core_components (especially the former), i.e. Python wrappers for React components. And this is of course really valuable and is basically the reason I use Dash,

My point, however, is that writing plain HTML is way too verbose with dash_html_components. In my view, both Jinja2 and dominate library provide more elegant way to template your HTML code. Dash is great for simple MI (Tableau-like) tasks, but as soon as you need something more complex it starts getting in your way.

Imagine a complex web app where you'd like to use a few Plotly graphs here and there. And suppose you want the cross-filtering on those graphs, which is a breeze with Dash. But because the graphs are scattered over distant parts of your HTML DOM, it forces you to implement the whole DOM structure via dash_html_components (whereas what you really want is to simply inject a few Dash Core Components in various places of you web app). Another example is where you have two unrelated pieces on the same page which are most easily implemented with Dash, but again, these pieces are located in completely different parts of DOM. And no, iframe is not a good solution to this. It is not even a feasible solution in the first case, and is a very poor solution to the second one. SEO considerations aside, I often want to interact with Dash part of my web app from outside (i.e. from plain javascript), and I want to do it directly without the need for postMessage etc.

I'm not too familiar with React, but let me summarize why I think dash_html_components exist:

  1. We need to have a single top-level React component (in Dash front-end)
  2. It is nice to have the ability to modify any part of your HTML markup through Dash Python callbacks/React,

After brief googling I came across the project aknuds1/html-to-react which seems like it could be solution to this problem. I can imagine the following workflow (I'm going to use Jinja2 as an example, but this would allow us to create HTML markup any way we like, including e.g. dominate):

  1. I write a Jinja2 template which is a mixture regular Flask HTML templating and dash_core_components. Where a Dash Core Component gets injected into the HTML the following happens:
    a) It returns simple div with a unique id, such as <div id='dash-core-component-wldjakle'>
    b) The actual Dash Component (which is what is currently being returned) is simply stored somewhere in Dash application instance.

As a result, the layout is no longer a hierarchy of Components, but rather a plain HTML string.

  1. When the response is sent back to Dash front-end, it contains the following:
    a) The string with layout HTML which we discussed above
    b) All Components which we have stored in Dash application, serialized to JSON - this is similar to what the current rendering method returns, but instead of creating a large nested object, it would just be a list of components.

  2. The following would happen on the front-end:
    a) HTML string gets parsed with aknuds1/html-to-react library (it's easy to convert it from a Node module to plain javascript with e.g. Browserify or Webpack). It creates React DOM structure.
    b) All Dash Core Components can be injected into this DOM structure by replacing their placeholder divs (which can be identified by unique ids).

Unless I'm missing something, as a result of this we'd get the same React DOM structure as we currently do. We satisfy both initial requirements - we still have just one top-level React component, and all plain HTML elements are part of React system, so could be easily interacted with from Dash Python callbacks.

Only other thing I can think of which is currently done on Python side is validation of all component ids between registered callbacks and layout. While no longer possible to do on the Python side (unless you parse HTML to DOM with e.g. lxml parser, which is also easy enough), it could be easily done on front-end side once html is converted to React DOM.

I've described what happens with initial layout, but the exact same thing could be applied to Dash callbacks which return children for some element.

With this approach you no longer need dash_html_components at all, so one less thing to maintain. If you want to generate DOM in Python, just use dominate - it's more elegant, flexible and very mature/robust. If you want to preserve backwards compatibility you could keep dash_html_components interface and just make it use dominate underneath (which should be extremely trivial).

Finally, not related to the above - for those who are happy to use dash_html_components, it would still be really handy if they supported with syntax similar to dominate - it would make the code much more compact, elegant and readable.

@masdeseiscaracteres
Copy link

I wrote something to address this very same concern:
https://github.com/masdeseiscaracteres/dash-html-template

It is just a proof-of-concept that could benefit a lot from the ideas above. My original idea was to be able to inject Dash components into Jinja templates but I ended up specifying my own sort of "templating format".

@ssword
Copy link

ssword commented May 8, 2018

I totally agree with @nirvana-msu. Yes, we do have dash-html-components, which is pretty handy at creating html components. But what about html structure? As far as I can see, most of the show and tell projects are merely "stacks" of html components. How do we control the structure of our application? In the layout page of your tutorial, I can only find out a layout that is controlled by style={'columnCount': 2}. What if we need more than this?
On my current project, I want to add bootstrap support for the application. This is the part of the code for a corner of the page:

html.Div(className='content', children=[
        html.Div(className='container-fluid', children=[
            html.Div(className='row', children=[
                html.Div(className='col-lg-3 col-md-6 col-sm-6', children=[
                    html.Div(className='card card-stats', children=[
                        html.Div(className='card-header',
                            **{'data-background-color': 'orange'},
                            children=[
                            html.I(className='fa fa-file-alt')
                            ]),
                        html.Div(className='card-body', children=[
                            html.Div(style={'font-family':'Roboto','font-weight':'bold'}, 
                            children=[
                                 html.H3(className='title', children=[
                                    html.B('Textual Analysis')], 
                                )
                            ]),
                            html.Hr(),
                            html.Div(className='alert alert-info alert-with-icon',
                            **{'data-notify': 'container'},
                            children=[
                                    html.I(className='fa fa-check-square',
                                        **{'data-notify': 'icon'}),
                                    html.Button(className='close',
                                    **{'data-dismiss':'alert',
                                        'aria-label':'Close'},
                                        type='button', 
                                        children=[
                                        html.I('close', className='material-icons')
                                    ]),
                                    html.Span(**{'data-notify':'message'},
                                    children=[
                                        html.B('Check to select all items')],
                                    ),
                                ],   
                            ),
                            html.Div(className='col-lg-3 col-md-6 col-sm-6',
                            style={'background-color': 'yellow'},
                            children=[

                                html.H4(className='card-title', children=[
                                    html.I(className='fa fa-hospital fa-w-14 fa-1x grape5',
                                        **{'data-notify': 'icon'}),
                                    'Facilities'
                                ])

                            ]),
                            dcc.Dropdown(
                                options=[
                                    {'label': 'New York City', 'value': 'NYC'},
                                    {'label': 'Montréal', 'value': 'MTL'},
                                    {'label': 'San Francisco', 'value': 'SF'}
                                ],
                                multi=True,
                                value='MTL'
                            )
                        ])
                    ])
                ])
            ])
        ])
    ])

I am new to this, maybe I am on the wrong track, but I don't know how else I can do it.

@rvsingh011
Copy link

Same problem, When we want a full fledged application. We need a lot of html.

@jkseppan
Copy link

Here's my attempt at turning an HTML-like language with embedded Python into calls to Dash constructors: https://github.com/jkseppan/htexpr/

With that you can write @ssword's example like this:

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

from htexpr import compile

app = dash.Dash(__name__, external_stylesheets=[
    'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'
], external_scripts=[
    'https://code.jquery.com/jquery-3.3.1.slim.min.js',
    'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js',
    'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js'
])

app.layout = compile("""
<div class='content'>
  <div class='container-fluid'>
    <div class='row'>
      <div class='col-lg-3 col-md-6 col-sm-6'>
        <div class='card card-stats'>
          <div class='card-header' data-background-color='orange'>
            <i class='fa fa-file-alt' />
          </div>
          <div class='card-body'>
            <div style={'font-family':'Roboto','font-weight':'bold'}>
              <h3 class='title'><b>Textual Analysis</b></h3>
              <hr />
              <div class='alert alert-info alert-with-icon' data-notify='container'>
		<i class='fa fa-check-square' data-notify='icon' />
		<button class='close' 
			data-dismiss='alert'
			aria-label='Close'
			type='button'>
                  <i class='material-icons'>close</i>
		</button>
                <span data-notify='message'>
                  <b>Check to select all items</b>
                </span>
              </div>
              <div class='col-lg-3 col-md-6 col-sm-6' style={'background-color': 'yellow'}>
		<h4 class='card-title'>
                  <i class='fa fa-hospital fa-w-14 fa-1x grape5' data-notify='icon' />
                  Facilities
		</h4>
              </div>
              <Dropdown
                options=[
                  {'label': 'New York City', 'value': 'NYC'},
                  {'label': 'Montréal', 'value': 'MTL'},
                  {'label': 'San Francisco', 'value': 'SF'}
                ]
                multi={True}
                value='MTL' />
	    </div>
	  </div>
	</div>
      </div>
    </div>
  </div>
</div>
""").run()

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

This is almost HTML, but you have to close all tags (but <hr /> is recognized), attributes can take Python values, and you can embed Python code. It gets compiled into a sequence of calls to Dash constructor functions (html.Div(className='content', children=[html.Div(...)])).

Forum post: https://community.plot.ly/t/htexpr-write-dash-layouts-in-html-syntax/21669

byronz pushed a commit that referenced this issue Apr 23, 2019
@amitripshtos
Copy link

Great work @jkseppan, really helpful for me since writing HTML is much faster for me.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this issue May 22, 2021
* commit generated files to the repo

- This is another sanity check for prerelease checks
- Enables users to install directly from GitHub. For Python users,
installing from PyPI is still the recommended way to install the package

* let eslint ignore generated bundles
HammadTheOne pushed a commit to HammadTheOne/dash that referenced this issue May 28, 2021
* commit generated files to the repo

- This is another sanity check for prerelease checks
- Enables users to install directly from GitHub. For Python users,
installing from PyPI is still the recommended way to install the package

* let eslint ignore generated bundles
HammadTheOne pushed a commit that referenced this issue Jul 23, 2021
* commit generated files to the repo

- This is another sanity check for prerelease checks
- Enables users to install directly from GitHub. For Python users,
installing from PyPI is still the recommended way to install the package

* let eslint ignore generated bundles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants