Реагировать.js: onChange событие для contentEditable


Как я могу слушать изменить событие для contentEditableуправления?

var Number = React.createClass({
    render: function() {
        return <div>
            <span contentEditable={true} onChange={this.onChange}>
                {this.state.value}
            </span>
            =
            {this.state.value}
        </div>;
    },
    onChange: function(v) {
        // Doesn't fire :(
        console.log('changed', v);
    },
    getInitialState: function() {
        return {value: '123'}
    }    
});

React.renderComponent(<Number />, document.body);

http://jsfiddle.net/NV/kb3gN/1621/

6 74

6 ответов:

Edit: посмотреть ответ Себастьяна Лорбера, который исправляет ошибку в моей реализации.


используйте событие onInput и, возможно, onBlur в качестве запасного варианта. Вы можете сохранить предыдущее содержимое, чтобы предотвратить отправку дополнительных событий.

Я бы лично, так как моя функция рендеринга.

var handleChange = function(event){
    this.setState({html: event.target.value});
}.bind(this);

return (<ContentEditable html={this.state.html} onChange={handleChange} />);

jsbin

который использует эту простую оболочку вокруг contentEditable.

var ContentEditable = React.createClass({
    render: function(){
        return <div 
            onInput={this.emitChange} 
            onBlur={this.emitChange}
            contentEditable
            dangerouslySetInnerHTML={{__html: this.props.html}}></div>;
    },
    shouldComponentUpdate: function(nextProps){
        return nextProps.html !== this.getDOMNode().innerHTML;
    },
    emitChange: function(){
        var html = this.getDOMNode().innerHTML;
        if (this.props.onChange && html !== this.lastHtml) {

            this.props.onChange({
                target: {
                    value: html
                }
            });
        }
        this.lastHtml = html;
    }
});

редактировать 2015

кто-то сделал проект на NPM с моим решением:https://github.com/lovasoa/react-contenteditable

изменить 06/2016: Я только что включил новую проблему, которая возникает, когда браузер пытается "переформатировать" html, который вы только что дали ему, что приводит к тому, что компонент всегда перерисовывается. посмотреть

изменить 07/2016: вот моя продукция contentEditable реализации. Он имеет некоторые дополнительные опции над react-contenteditable что вы можете хотеть, в том числе:

  • замок
  • императивный API, позволяющий вставлять фрагменты html
  • возможность форматировать контент

резюме:

решение FakeRainBrigand работало довольно хорошо для меня в течение некоторого времени, пока я не получил новые проблемы. ContentEditables-это боль,и с ней не очень легко справиться...

этот JSFiddle демонстрирует проблему.

как вы можете видеть, когда вы вводите несколько символов и нажмите на кнопку Clear содержимое не очищается. Это связано с тем, что мы пытаемся сбросить contenteditable до последнего известного значения виртуального dom.

так кажется, что:

  • вам нужно shouldComponentUpdate для предотвращения скачков положения вставки
  • вы не можете полагаться на алгоритм VDOM diffing React, если вы используете shouldComponentUpdate этот путь.

так вам нужна дополнительная строка, так что всякий раз, когда shouldComponentUpdate возвращает да, вы уверены, что содержание DOM на самом деле обновляется.

так что версия здесь добавляет componentDidUpdate и будет:

var ContentEditable = React.createClass({
    render: function(){
        return <div id="contenteditable"
            onInput={this.emitChange} 
            onBlur={this.emitChange}
            contentEditable
            dangerouslySetInnerHTML={{__html: this.props.html}}></div>;
    },

    shouldComponentUpdate: function(nextProps){
        return nextProps.html !== this.getDOMNode().innerHTML;
    },

    componentDidUpdate: function() {
        if ( this.props.html !== this.getDOMNode().innerHTML ) {
           this.getDOMNode().innerHTML = this.props.html;
        }
    },

    emitChange: function(){
        var html = this.getDOMNode().innerHTML;
        if (this.props.onChange && html !== this.lastHtml) {
            this.props.onChange({
                target: {
                    value: html
                }
            });
        }
        this.lastHtml = html;
    }
});

виртуальный dom остается устаревшим, и это может быть не самый эффективный код, но по крайней мере он работает :) моя ошибка устранена


детали:

1) Если вы ставите shouldComponentUpdate, чтобы избежать скачков каретки, то contenteditable никогда не повторяет (по крайней мере, при нажатии клавиш)

2) Если компонент никогда не повторяется при нажатии клавиши, то React сохраняет устаревший виртуальный dom для этого contenteditable.

3) Если React сохраняет устаревшую версию contenteditable в своем виртуальном дереве dom, то если вы попытаетесь сбросить contenteditable до значения устаревшего в виртуальном dom, то во время виртуального DOM diff React вычислит, что нет никаких изменений для применения к виртуальному DOM Дом!

это происходит в основном, когда:

  • у вас есть пустой contenteditable изначально (shouldComponentUpdate=true, prop="", предыдущий vdom=N / A),
  • пользователь вводит некоторый текст, и вы предотвращаете визуализацию (shouldComponentUpdate=false, prop=text, предыдущий vdom="")
  • после того, как пользователь нажимает кнопку проверки, вы хотите очистить это поле (shouldComponentUpdate=false,prop="",предыдущий vdom="")
  • как недавно произведенных и старый вдом являются "", реагировать не трогать дом.

это, вероятно, не совсем тот ответ, который вы ищете, но, борясь с этим сам и имея проблемы с предлагаемыми ответами, я решил сделать его неконтролируемым вместо этого.

, когда editable опора false, Я использую text проп как есть, но когда это true, я переключаюсь в режим редактирования, в котором text не имеет никакого эффекта (но по крайней мере браузер не психуй). За это время onChange запускаются с помощью управления. Наконец, когда я меняю editable на false, он заполняет HTML с тем, что было передано в text:

/** @jsx React.DOM */
'use strict';

var React = require('react'),
    escapeTextForBrowser = require('react/lib/escapeTextForBrowser'),
    { PropTypes } = React;

var UncontrolledContentEditable = React.createClass({
  propTypes: {
    component: PropTypes.func,
    onChange: PropTypes.func.isRequired,
    text: PropTypes.string,
    placeholder: PropTypes.string,
    editable: PropTypes.bool
  },

  getDefaultProps() {
    return {
      component: React.DOM.div,
      editable: false
    };
  },

  getInitialState() {
    return {
      initialText: this.props.text
    };
  },

  componentWillReceiveProps(nextProps) {
    if (nextProps.editable && !this.props.editable) {
      this.setState({
        initialText: nextProps.text
      });
    }
  },

  componentWillUpdate(nextProps) {
    if (!nextProps.editable && this.props.editable) {
      this.getDOMNode().innerHTML = escapeTextForBrowser(this.state.initialText);
    }
  },

  render() {
    var html = escapeTextForBrowser(this.props.editable ?
      this.state.initialText :
      this.props.text
    );

    return (
      <this.props.component onInput={this.handleChange}
                            onBlur={this.handleChange}
                            contentEditable={this.props.editable}
                            dangerouslySetInnerHTML={{__html: html}} />
    );
  },

  handleChange(e) {
    if (!e.target.textContent.trim().length) {
      e.target.innerHTML = '';
    }

    this.props.onChange(e);
  }
});

module.exports = UncontrolledContentEditable;

вот компонент, который включает в себя многое из этого lovasoa:https://github.com/lovasoa/react-contenteditable/blob/master/index.js

он перекрывает событие в emitChange

emitChange: function(evt){
    var html = this.getDOMNode().innerHTML;
    if (this.props.onChange && html !== this.lastHtml) {
        evt.target = { value: html };
        this.props.onChange(evt);
    }
    this.lastHtml = html;
}

Я использую подобный подход успешно

Я предлагаю использовать mutationObserver для этого. Это дает вам гораздо больше контроля над тем, что происходит. Он также дает вам более подробную информацию о том, как обзор интерпретирует все нажатия клавиш

здесь в TypeScript

import * as React from 'react';

export default class Editor extends React.Component {
    private _root: HTMLDivElement; // Ref to the editable div
    private _mutationObserver: MutationObserver; // Modifications observer
    private _innerTextBuffer: string; // Stores the last printed value

    public componentDidMount() {
        this._root.contentEditable = "true";
        this._mutationObserver = new MutationObserver(this.onContentChange);
        this._mutationObserver.observe(this._root, {
            childList: true, // To check for new lines
            subtree: true, // To check for nested elements
            characterData: true // To check for text modifications
        });
    }

    public render() {
        return (
            <div ref={this.onRootRef}>
                Modify the text here ...
            </div>
        );
    }

    private onContentChange: MutationCallback = (mutations: MutationRecord[]) => {
        mutations.forEach(() => {
            // Get the text from the editable div
            // (Use innerHTML to get the HTML)
            const {innerText} = this._root; 

            // Content changed will be triggered several times for one key stroke
            if (!this._innerTextBuffer || this._innerTextBuffer !== innerText) {
                console.log(innerText); // Call this.setState or this.props.onChange here
                this._innerTextBuffer = innerText;
            }
        });
    }

    private onRootRef = (elt: HTMLDivElement) => {
        this._root = elt;
    }
}

Это самое простое решение, которое сработало для меня.

<div
  contentEditable='true'
  onInput = {e => {console.log('text of div', e.currentTarget.textContent)}}
>
Text in div
</div>