【问题标题】:Get selected text in a Dash application在 Dash 应用程序中获取选定的文本
【发布时间】:2020-11-11 22:22:51
【问题描述】:

我想在用户按下按钮时接收用户选择的文本。

背景是:

我想创建一个应用来标记文本序列(用于 NLP 应用)。用户会:

  • 查看呈现的文本
  • 选择一段文字
  • 按下按钮确认选择
  • 重复

因此,一旦用户按下按钮,我希望接收当前选择的文本作为输入。

Dash 有可能吗?在 Dash 中,您可以将回调附加到组件属性。所以,我一直在寻找一个捕获文本选择的属性。不幸的是,html.Divhtml.Phtml.Span 没有它。 dcc.Input 有,但仅限于一行,我需要显示多行文本。而dcc.Textarea 虽然不限于一行,但同样没有这样的属性。

【问题讨论】:

  • 您可以使用破折号或panel轻松做到这一点
  • 这让我很乐观 :) 但是如何呢?我没有找到用 Dash 做到这一点的方法。到目前为止,我编辑了添加我的发现的问题。
  • 我觉得在这种情况下面板会是一个更好的工具。鉴于您已经有了一个项目符号列表,您就可以开始构建您的项目了。
  • 您希望它作为具有多个用户的 Web 应用程序工作,还是仅在一台计算机和一个用户在同一台​​计算机上工作?
  • 如果您找不到现成的python解决方案,您可以使用例如react-highlight并制作您的own Dash component

标签: python plotly plotly-dash


【解决方案1】:

我找到了一个可以开始的解决方案。让我们从代码开始,接下来会有解释。

这是python代码:

import dash
import dash_html_components as html
import dash_core_components as dcc

app = dash.Dash(__name__)
box_style = dict(border='1px solid', margin='15px', padding='10px')
app.layout = html.Div(id='wrapper', children=[
    html.P(id='selection-container', children='This text is selectable. Select here.', style=box_style),
    html.P(id='other-container', children='Selecting this text will not work.', style=box_style),
    dcc.Input(id='selection-target', value='', style=dict(display='none')),
    html.Button(id='submit', children='Submit selection'),
    html.P(id='callback-result', children=''),
])


@app.callback(
    dash.dependencies.Output('callback-result', 'children'),
    [dash.dependencies.Input('submit', 'n_clicks')],
    [dash.dependencies.State('selection-target', 'value')],)
def update_output(n_clicks, value):
    if value:
        return html.Span(f'Selected string: "{value}"', style=dict(color='green'))
    return html.Span('Nothing selected', style=dict(color='red'))


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

现在是一些 JavaScript。您需要将其放在名为“assets”的目录下的 JS 文件中(例如 assets/custom.js;解释为 here):

document.addEventListener('mouseup', () => {
  var sel = window.getSelection();
  var sel_str = sel.toString();
  var target_value = '';
  if (sel_str.length > 0) {
    var pid = sel.focusNode.parentNode.id;
    if (pid == 'selection-container') {
      target_value = sel_str;
    }
  }
  // Way to set value of React input
  var input = document.getElementById('selection-target');
  var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
  nativeInputValueSetter.call(input, target_value);
  var ev2 = new Event('input', {bubbles: true});
  input.dispatchEvent(ev2);

});

现在解释一下:代码创建了两个 HTML P 元素,一个是可选的,另一个不是。当在可选框中选择文本并单击按钮时,所选文本通过回调显示在第三个输出 P 中。这是通过使用所选文本更新隐藏文本输入的值来实现的(使用与 React 框架兼容的方法)。单击该按钮会触发一个回调,该回调将该值作为状态接收。隐藏的输入可以更改为任何其他类型的输入,我想文本区域也可以,但我还没有测试过。

有几点需要注意:JS 事件捕获有点古怪,而且远未完成。捕获文档的“mouse up”事件并不理想,您应该能够想到更好的方法。此外,如果您从不可选择的 P 开始选择并向后选择到可选择的 P,则该事件将被触发,因此您也需要注意这一点。

希望这会有所帮助。请用您最终选择的解决方案更新我们。

【讨论】:

  • 不幸的是,当您编写答案时,我已经完成了创建自定义 Dash 组件的一半。现在我已经完成了,可能明天会发布结果,以便其他人也可以使用它。事实证明,@np8 在他或她的评论中发布的优秀文档和示例组件存储库比预期的要容易。
  • @Konstantin 您能否发布您的解决方案(如果可能)?
【解决方案2】:

我能够使用 Dash-React 集成解决问题,遵循 thisthis 指南,并使用以下 JavaScript 代码:

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


function debounce(func, wait, immediate) {
  let timeout;

  return function () {
    const context = this, args = arguments;
    const later = () => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    const callNow = immediate && !timeout;

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
}


export default class Highlightable extends Component {

  mouseEvent() {

    let text = '';

    if (window.getSelection) {
      text = window.getSelection().toString();
    }

    if(!text || !text.length) {
      return false;
    }

    this.props.setProps({
      selected_text: text,
      selection_start: window.getSelection().anchorOffset,
    })
  }

  onMouseUp(event) {
    debounce(() => {
      if (this.doucleckicked) {
        this.doucleckicked = false;
        this.dismissMouseUp++;
      } else if(this.dismissMouseUp > 0) {
        this.dismissMouseUp--;
      } else {
        this.mouseEvent.bind(this)();
      }
    }, 200).bind(this)();
  }

  onDoubleClick(event) {
    event.stopPropagation();

    this.doucleckicked = true;
    this.mouseEvent.bind(this)();
  }

  render() {
    return (
        <div id={this.props.id}
             style={{
               whiteSpace: 'pre-wrap',
               display: 'inline-block',
               verticalAlign: 'text-top',
               margin: '10px',
               width: this.props.width}}
             onMouseUp={this.onMouseUp.bind(this)}
             onDoubleClick={this.onDoubleClick.bind(this)}>
          {this.props.text}
        </div>
    );
  }
}

Highlightable.defaultProps = {};

Highlightable.propTypes = {
  id: PropTypes.string,
  width: PropTypes.string,
  text: PropTypes.string.isRequired,
  selected_text: PropTypes.string,
  selection_start: PropTypes.number,
  selection_end: PropTypes.number,
  /**
   * Dash-assigned callback that should be called to report property changes
   * to Dash, to make them available for callbacks.
   */
  setProps: PropTypes.func
};

【讨论】:

    猜你喜欢
    • 2013-11-27
    • 2016-12-26
    • 2019-01-09
    • 2021-10-03
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多