Embed google streetview map using dbc.Carousel

I have a bunch of images that are being rendered using dbc.Carousel. The images are saved as a list of dicts that are iterated over and displayed in the carousel. Snippet of code below:

            img_dict = ast.literal_eval(img_dict)

            # Add labels for carousel items property
            img_list = []

            for key, values in img_dict.items():
                 for v in values:

                     img_dict1 = {"key": c, "src": v, "img_style": {"width": "300px", "height": "300px"}}
                     
                     img_list.append(img_dict1)

            carousel = dbc.Carousel(
                                    items=img_list,
                                    controls=True,
                                    indicators=True
                       )

I’d like to embed google streetview map instead of images. Specifically, streetview mode from the documentation here: Embedding a map  |  Maps Embed API  |  Google Developers

Looks something like this: Discover Street View and contribute your own imagery to Google Maps.

Does dbc.Carousel support this?

@AnnMarieW wondering if you have any thoughts on embedding streetview in carousel. :thinking:

perhaps, as an iframe?

Hi @keval

Currently, the Carousel component only accepts images. It would be pretty cool to be able to add video and iframes, but this would have to be added as a new feature. @tcbegley - what do you think of this idea?

1 Like

Interesting, off the top of my head I don’t see why an iframe or video inside the carousel wouldn’t be possible. Sounds like it’s definitely worth an experiment.

I guess one issue would be that you can’t pass react nodes via any other props than children, so you would have to change the interface to do so (currently, them carousel items are passed via the items prop).

Agreed, but much like how we currently create an img tag and pass the user-provided src to it, I don’t see a reason off the top of my head why we couldn’t let the user do something like items=[{"type": "iframe", "src": "https://plotly.com", …}, …] and Just make sure we create the corresponding tag internally. Of course this won’t let you pass arbitrary children, but specifically iframes and videos should be possible in the same way as images I think.

Ah, yes, that is true. However, that would require a lot of extra code in the React layer :confused: . It would be much easier, if we could just pass the IFrame component from Dash directly.

As a matter of fact, have been running into this issue (being unable to pass react node to any prop but children) a lot recently. Do you know, if there are plans to address it, @chriddyp ? I vaguely remember a related PR some time ago, but I haven’t noticed any recent activity.

Yeah it would be really great to address it somehow. To do it “right” where each component could be addressed with callbacks would be quite a bit of work as it’d touch almost all parts of the architecture. There’s quite a few performance challenges too.

However, I wonder if there would be an easier way to do it within the component code itself where the components wouldn’t necessarily be addressable by callbacks but would at least render. Perhaps by calling React.createElement within your component:

React.createElement(
     window[props.items[0].namespace[props.items[0].type]],
     props.items[0].props,
     props.items[0].children
)

Where props.items would be e.g.:

dbc.Carousel(items=html.Div('hello'))

which gets serialized and passed to your component as:

{
    "type": "Div",
    "namespace": "dash_html_components",
    "props": {"children": "hello world"}
}

This wouldn’t be recursive unless you wrote it so ( (i.e. children couldn’t contain components itself, unless you wrote that recursive rendering in yourself). Also, even if you gave these components an ID they wouldn’t be addressable by Output. However, there might be a way to get these components to render like this as of today.

Thank you for the outline, @chriddyp ! I guess I would have to dive into the code to figure out what changes are actually needed, but could you put a few words on why we couldn’t just treat all properties annotated with PropTypes.node the way the children property is currently treated?

I just tried out your suggestion. First, the Python library raises an exception if you try to pass a component for a prop other than children. Silencing this exception, I get the next error in the React layer,

Invalid argument `item` passed into TestComponent.
Expected a ReactNode.

As an alternative, I tried just using PropTypes.object. Using the following render code,

const item = React.createElement(
        window[props.item.namespace][props.item.type],
        omit(["setProps", "children"], props.item.props),
        props.item.props.children
   )
    return (
        <div>
            {item}
        </div>
    );

it actually works(!). I guess this could be a workaround until a proper fix is implemented in the Dash renderer. I’ll play around a bit more, and then I’ll try to wrap up a small utility library to hide the pain :slight_smile:

2 Likes

I have made the code a bit more robust (handling of lists, raw strings, nested structure, etc.) and packaged it into dash-extensions-js@0.0.3. Hence, the react code is now along the lines of (excluding prop type defs),

import { renderDashComponent } from "dash-extensions-js";

const TestComponent = (props) => {
    return (
        <div>
            {renderDashComponent(props.item)}
        </div>
    );
};

which enables Dash code like,

app.layout = html.Div([
    TestComponent(),
    TestComponent(item=None),
    TestComponent(item=[]),
    TestComponent(item=""),
    TestComponent(item="Raw text (no list)"),
    TestComponent(item=["Raw text (list)"]),
    TestComponent(item=html.Div("Single item (no list)")),
    TestComponent(item=[html.Div("Single item (list)")]),
    TestComponent(item=html.Div(html.Div("Single item (nested)"))),
    TestComponent(item=html.Div(html.Div(html.Div("Single item (double nested)")))),
    TestComponent(item=TestComponent(item="Nested non-children")) 
])

I believe this approach would fit nicely for the Carousel component usecase :slight_smile:

3 Likes

Doing a bit more debugging, I have noticed some output to the console (even though everything renders as intended). The first one is something like,

react_devtools_backend.js:4045 Warning: me: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://fb.me/react-special-props)
    in me (created by Accordion)
    in Accordion (created by CheckedComponent)
    in CheckedComponent (created by BaseTreeContainer)
    in ComponentErrorBoundary (created by BaseTreeContainer)
    in BaseTreeContainer (created by Context.Consumer)
    in Unknown (created by UnconnectedContainer)
    in div (created by UnconnectedGlobalErrorContainer)
    in div (created by GlobalErrorOverlay)
    in div (created by GlobalErrorOverlay)
    in GlobalErrorOverlay (created by DebugMenu)
    in div (created by DebugMenu)
    in DebugMenu (created by UnconnectedGlobalErrorContainer)
    in div (created by UnconnectedGlobalErrorContainer)
    in UnconnectedGlobalErrorContainer (created by withRadiumContexts(UnconnectedGlobalErrorContainer))
    in withRadiumContexts(UnconnectedGlobalErrorContainer) (created by Connect(withRadiumContexts(UnconnectedGlobalErrorContainer)))
    in Connect(withRadiumContexts(UnconnectedGlobalErrorContainer)) (created by UnconnectedContainer)
    in UnconnectedContainer (created by Connect(UnconnectedContainer))
    in Connect(UnconnectedContainer) (created by UnconnectedAppContainer)
    in UnconnectedAppContainer (created by Connect(UnconnectedAppContainer))
    in Connect(UnconnectedAppContainer) (created by AppProvider)
    in Provider (created by AppProvider)
    in AppProvider

as the component loads. The second is,

react-dom@16.v2_0_0m1640104809.14.0.js:321 Uncaught TypeError: n.setProps is not a function
    at onClick (I.react.js:19:34)
    at HTMLUnknownElement.callCallback (react-dom@16.v2_0_0m1640104809.14.0.js:182:16)
    at Object.invokeGuardedCallbackDev (react-dom@16.v2_0_0m1640104809.14.0.js:231:18)
    at invokeGuardedCallback (react-dom@16.v2_0_0m1640104809.14.0.js:286:33)
    at invokeGuardedCallbackAndCatchFirstError (react-dom@16.v2_0_0m1640104809.14.0.js:300:27)
    at executeDispatch (react-dom@16.v2_0_0m1640104809.14.0.js:383:5)
    at executeDispatchesInOrder (react-dom@16.v2_0_0m1640104809.14.0.js:405:9)
    at executeDispatchesAndRelease (react-dom@16.v2_0_0m1640104809.14.0.js:3401:7)
    at executeDispatchesAndReleaseTopLevel (react-dom@16.v2_0_0m1640104809.14.0.js:3410:12)
    at forEachAccumulated (react-dom@16.v2_0_0m1640104809.14.0.js:3382:10)

I guess the first one might be related to some Dash wrapping, which doesn’t know about my “customly rendered component”? Is there anything that I can do? The second part, I am a bit more puzzled about…

I think I have figured out the second part; Dash tries to invoke setProps, but that property doesn’t exist on the customly rendered component. Assigning an empty function to setProps solves this issue. This fix has been implemented in dash-extensions-js@0.0.6.

I still haven’t figured out how to address the warning. However, I have noticed that it appears only in debug mode. When debug is set to false, I don’t see any errors (or warnings). Do you know what is wrong, @chriddyp? :slight_smile: