nteract / vdom

🎄 Virtual DOM for Python
https://github.com/nteract/vdom/blob/master/docs/mimetype-spec.md
BSD 3-Clause "New" or "Revised" License
221 stars 34 forks source link

Design of updating existing display objects #83

Open willingc opened 5 years ago

willingc commented 5 years ago

From #67

Do we want to have a discussion about or make a design decision on how we want to support updated displays of vdom objects?

rgbkrk commented 5 years ago

I think we need to re-do those examples, to instead make functions that return vdom elements. There's a part of me that thinks an API that allows updates could be built on top of vdom (just using vdom for the representation to send across the wire).

rmorshea commented 5 years ago

@rgbkrk I think @gnestor and I talked about this in the last month or so. In terms of underlying design it might be similar to what I did with purly (i.e. sending diffs over the wire) though with a very different api.

allefeld commented 4 years ago

Just stumbled across vdom, so maybe what I'm saying doesn't make sense.

But, the name "vdom" to me suggests a DOM interface like that in JavaScript, which is powerful exactly because it allows modifying DOM objects.

The current method to update, which involves recreating elements appears extremely cumbersome. Say I have a deep element hierarchy, and I want to change some small detail within, and I have to recreate the whole tree?!

I haven't tested it, but doesn't that inevitably lead to flicker, or parts of the notebook flipping up and down on update?

allefeld commented 4 years ago

Why I care: https://github.com/jupyterlab/jupyterlab/issues/7873

I believe vdom has the potential to fill a big usability gap in JupyterLab, namely to

allow users to create interactive HTML-based displays with relatively little effort, especially without creating an extension

– but only if it enables in-place modification of elements.

rmorshea commented 4 years ago

@allefeld I’d take a look at my project idom (basically a second draft of purly mentioned above). It seems to do exactly what you’re looking for.

gnestor commented 4 years ago

@allefeld VDOM (virtual dom) is a concept underlying React. The idea is that we don't need to directly manipulate the DOM (because cumbersome) and some would argue that we shouldn't. The jQuery way of doing things exemplifies this and apps built using jQuery are difficult to reason about and even more difficult to debug. To me, the core issue is jQuery encourages you to store app state in the DOM, spread out across a bunch of nodes. VDOM simplifies this by making UI a function of state ((state) => UIComponent(state)). This is great in the context of a Jupyter notebook because a user only needs to declare how their data maps to the virtual DOM and anytime their data changes, VDOM will update the DOM using the minimum DOM operations.

VDOM is not perfect but I've used it extensively and it's a heck of a lot better than using IPython.HTML. One of the best things about VDOM is the ability to create VDOM components and compose those components to create more elaborate UI. I recently added event handler support to VDOM so that you can create interactive UI with it, but there are some limitations to it that would probably require a rewrite. This allows you to create ipywidget-like interactive UIs with a fraction of the effort.

If you need to manipulate the DOM directly, then VDOM is not the right tool.

allefeld commented 4 years ago

@gnestor, thanks for the explanation!

The reason I posted is that I'm working on a Python object inspector (see jupyterlab/jupyterlab#7873), represented as nested unordered lists, and I need to create new sublists in response to user interaction (click). How would I go about implementing this using vdom? From what I can gather, I would have to recreate the whole nested lists every time, which indeed suggests that vdom is not the right tool.

allefeld commented 4 years ago

@rmorshea, thanks for suggesting idom, it indeed looks like it has exactly the functionality I want. What lets me hesitate is the need to create mini-webservers. That makes sense if the goal is to create Python-backed websites / apps, but suboptimal within Jupyter, where there's already a server running. Maybe I misunderstand?

rmorshea commented 4 years ago

@allefeld IDOM does in fact require you to create a simple socket server to send DOM updates to the frontend because it's not inherently tied to the Jupyter ecosystem. With that said, if there were enough interest and I found the time, there's no reason that it couldn't integrate directly into Jupyter's comm interface (thus negating the need for a socket server).

gnestor commented 4 years ago

@allefeld Theoretically, you would just update the display of the cell containing the JSON tree visualization with the new JSON for the Python variable (e.g. output_handle.update(json_component(new_json)))) and VDOM (more specifically React in the vdom jupyterlab extension) would handle computing the diff between the existing DOM and your new virtual DOM and apply the minimum number of DOM operations to get the DOM to reflect your new Python variable state.

allefeld commented 4 years ago

apply the minimum number of DOM operations to get the DOM to reflect your new Python variable state

Alright, that's the part I didn't realize. Thanks again!

rmorshea commented 4 years ago

@allefeld it may be worth noting that for "large" views there may be some performance limitations since:

I'm not really sure how "large" the view needs to be before you experience any slowdown though. In the past at least, when I've run tests locally with Jupyterlab, views with ~500 elements may started to take 0.1 seconds to update. @gnestor probably has a better sense of the performance limitations though.

rmorshea commented 4 years ago

Ok, here's an example which demonstrates these performance limitations. This 25x25 grid of colors shifts the colors in a row to the right when clicked. While for many use cases the performance is acceptable, it's definitely on the sluggish side. Scale this up to 50x50, and things really start to chug:

from vdom.helpers import div

colors = ["red", "blue", "purple", "green", "orange", "yellow"]
color_shift_state = {}

def color_grid(x_size, y_size):
    def update():
        handle.update(color_grid(x_size, y_size))

    return div(
        *[
            color_row(y_size, update, i, color_shift_state.get(i, 0))
            for i in range(x_size)
        ]
    )

def color_row(y_size, update, row_index, count):
    return div(
        *[color_row_item(update, row_index, i, count) for i in range(y_size)],
        style={"height": "15px"},
    )

def color_row_item(update, row_index, col_index, count):
    def shift_row_colors(event):
        color_shift_state[row_index] = count + 1
        update()

    return div(
        onClick=shift_row_colors,
        style={
            "backgroundColor": colors[(count + col_index) % len(colors)],
            "height": "15px",
            "width": "15px",
            "display": "inline-block",
        },
    )

handle = display(color_grid(25, 25), display_id=True)

If this same example were implemented using a solution like idom, it would not suffer the same performance limitations because it only sends the part of the view which actually changed over the wire.