plotly / dash-labs

Work-in-progress technical previews of potential future Dash features.
MIT License
139 stars 39 forks source link

Multi-Page Apps [Feature Request] Customized meta data based on path variables #74

Closed AnnMarieW closed 2 years ago

AnnMarieW commented 2 years ago

As requested on the forum:

I would love to see this include customizing the meta data based on the path variables.

Example: We might have urls like /news/sports/1 and /news/sports/2. Each of these articles cover different topics and the article title and overview should be displayed when sharing instead of a generic text about sports articles.

eliasdabbas commented 2 years ago

This would make Dash more powerful in terms of SEO.

+1

AnnMarieW commented 2 years ago

So here's one potential solution. Does this make sense? Is there a better way?


This is how to register a page with path variables in the pages/ folder. Note that the layout is in this file:

pages.path_variables.py

import dash

dash.register_page(
    __name__,
    path_template="/news/<section>/<topic>",
    title="Plotly Times - World News" ,
    description="The Plotly Times.  All the data that's fit to plot.  World news topic 1",
    path="/news/world/1"
)

def layout(section=None, topic=None, **other_unknown_query_strings):
    return dash.html.Div(
        f"variables from pathname:  section: {section} topic: {topic}"
    )

You can create additional paths, with a different title, description and image that points to the layout above. They are added to the dash.page_registry dict like this:

util.py

import dash

def register_paths():
    dash.register_page(
        "path_variables_path1",
        path_template="/news/<section>/<topic>",
        title="Plotly Times - Sports",
        description="The Plotly Times.  All the data that's fit to plot.  Sports news topic 1",
        path="/news/sports/1",
        layout=dash.page_registry["pages.path_variables"]["layout"],
    )

    dash.register_page(
        "path_variables_path2",
        path_template="/news/<section>/<topic>",
        title="Plotly Times - Sports",
        description="The Plotly Times.  All the data that's fit to plot.  Sports news topic 2",
        path="/news/sports/2",
        layout=dash.page_registry["pages.path_variables"]["layout"],
    )

The function above is called from the main app.py. It's necessary to do it this way because the function must be called after all the layouts have been registered.

It works! Topic 2


image

Here's Topic 1

image

Just need to removed these two lines from the _path_to_page function

AnnMarieW commented 2 years ago

The above is not a great solution for more dynamic pages as mentioned on the forum - Thanks @raptorbrad for your feedback:


I supposed that solution does work, but I don’t think it’s ideal for more dynamic page. I’m not fully aware of how the meta tags work, but a solution I thought of is either:

I’m not sure if either of these are feasible, since I’m not too familiar with what’s going on under the hood.

bradley-erickson commented 2 years ago

I have no idea if the following solution will work. I have not tested it, but I at least theorized what it could be. I don't know how search engines/the sharing piece of meta tags work, so this theory might be valid. I would be afraid that this doesn't work as it requires the page loading.

I'm not sure if I'll get to playing around with this, but I think we could maybe inject the meta tag into the head using Javascript. If how I traced the correctly works out, we could do the following:

We return additional meta information here and merge it with the _ID_STORE data: https://github.com/plotly/dash-labs/blob/275745c817f5f2d9bddb4203d06e12e1362d96d4/dash_labs/plugins/pages.py#L353-L358

Then when that data changes, we have a clientside callback that injects the new metadata into the headers. We could use the already existing clientside_callback located below or we can create another one. https://github.com/plotly/dash-labs/blob/275745c817f5f2d9bddb4203d06e12e1362d96d4/dash_labs/plugins/pages.py#L384-L392

I found this StackOverflow post that covers how to inject the meta data.

Any thoughts? Does this type of approach even work?

AnnMarieW commented 2 years ago

@bradley-erickson Thanks for your comments and feedback. Yes, I think an approach like this could work if it's necessary to update the meta tags during in-app navigation. However, it's probably more important to update the meta-tags when a link to the page is shared like how it's done here.

I think the bigger question is how (rather than when) to update the meta tags when there are variables in the path. Currently there is only one set of metadata per page in the dash.page_registry. What's the best way to extend this so that the meta data can be a function of the path?

Perhaps we could use a dcc.Store component as you suggested, but it should probably be a separate component so it's independent of how pages/ uses _ID_STORE internally. This could be updated in a callback in the app so it's dynamic.

This dcc.Store could store a dict similar to dash.page_registry however the keys would be the path and it would only contain the meta data info. Any data here would override the current meta data info as generated in the _path_to_page function.

Well, that's what I have so far, I'm still open to other suggestions.

AnnMarieW commented 2 years ago

Here's another potential solution as suggested by @ chriddyp:

Allow the user to supply a function to dash.register_page for the title and description. For example

dash.register_page(
     path='/flowers/<flower_id>',
     title=lambda flower_id: return get_flower_title(flower_id)

We’ll probably need to have some kind of check for the URL with flask.request.path within our function that creates the index HTML

AnnMarieW commented 2 years ago

Done - PR coming soon. Here's a preview: https://dashlabs.pythonanywhere.com/asset/inventory/department/branch-2000