2

I would like to show the user a paragraph of text, and then ask the user to select part(s) of text. This text will then be analysed and an output will be shown in another component. How can I achieve this in Dash? This question is similar to this already answered question (Can Shiny recognise text selection with mouse (highlighted text)?), which solves the problem in Shiny.

Sainath Adapa
  • 99
  • 1
  • 6

1 Answers1

0

We can detect whether text is selected by adding event listeners in Javascript.

We are, at the time I'm writing this, limited in terms of the communication that is possible between Dash components and Javascript. Clientside callbacks allow us to execute Javascript code inside callbacks. We can't really use those here, because there isn't really a suitable Input we can use for detecting when text is highlighted.

We can instead create a custom Dash component with React that listens for a selectionchange event:

The selectionchange event of the Selection API is fired when the current text selection on a document is changed.

I've created the following custom component that allows us to detect when text is selected and persists this to a property so it can be used in Dash callbacks:

import React, {Component, useEffect, useState} from 'react';
import PropTypes from 'prop-types';

const SelectableWrapper = ({parentId, setProps, children}) => {
    useEffect(() => {
        const target = document.getElementById(parentId);

        function handleSelected(e) {
            setProps({selectedValue: document.getSelection().toString()});
        }

        document.addEventListener('selectionchange', handleSelected);

        return () => {
            document.removeEventListener('selectionchange', handleSelected);
        };
    }, []);
    return children;
};

/**
 * Contains a wrapper component which attaches an event that listens
 * for selection of elements in the document
 */
export default class DashSelectable extends Component {
    render() {
        const {id, setProps, children} = this.props;

        return (
            <div id={id}>
                <SelectableWrapper parentId={id} setProps={setProps}>
                    {children}
                </SelectableWrapper>
            </div>
        );
    }
}

DashSelectable.defaultProps = {};

DashSelectable.propTypes = {
    /**
     * The ID used to identify this component in Dash callbacks.
     */
    id: PropTypes.string,

    /**
     * Dash-assigned callback that should be called to report property changes
     * to Dash, to make them available for callbacks.
     */
    setProps: PropTypes.func,

    /**
     * Child components
     */
    children: PropTypes.node,

    /**
     * Selected value
     */
    selectedValue: PropTypes.string,
};

See this answer and the documentation here for more on creating custom Dash components with React.

A demo that shows how you could use this custom component in a Dash app:

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
from dash_selectable import DashSelectable

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        DashSelectable(
            id="dash-selectable",
            children=[html.P("Select this text"), html.P(id="output")],
        )
    ]
)


@app.callback(Output("output", "children"), [Input("dash-selectable", "selectedValue")])
def display_output(value):
    text = ""
    if value:
        text = value

    return "You have selected: {}".format(text)


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

The selectedValue property holds the value of what is currently selected in the document.

Everytime the selection changes this callback is executed.


I've put up this code on Github here and on pypi here.

If you want to use my package you can install it with pip install dash-selectable.

5eb
  • 14,798
  • 5
  • 21
  • 65