it-swarm.com.de

React.js: onChange-Ereignis für contentEditable

Wie kann ich das Ereignis für die contentEditable-basierte Steuerung ändern?

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/

90
NVI

Edit: Siehe Sebastien Lorbers Antwort was einen Fehler in meiner Implementierung behebt.


Verwenden Sie das onInput-Ereignis und optional onBlur als Fallback. Möglicherweise möchten Sie den vorherigen Inhalt speichern, um das Senden zusätzlicher Ereignisse zu verhindern.

Ich persönlich hätte dies als meine Renderfunktion.

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

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

jsbin

Welches verwendet diesen einfachen Wrapper um 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;
    }
});
66
Brigand

Edit 2015

Jemand hat ein NPM-Projekt mit meiner Lösung erstellt: https://github.com/lovasoa/react-contenteditable

Edit 06/2016: Ich bin gerade auf ein neues Problem gestoßen, das auftritt, wenn der Browser versucht, das HTML, das Sie ihm gerade gegeben haben, "neu zu formatieren", was dazu führt Komponente immer neu rendern. Siehe

Edit 07/2016: hier ist meine Produktion contentEditable Implementierung. Es gibt einige zusätzliche Optionen über react-contenteditable, Die Sie möglicherweise wünschen, einschließlich:

  • verriegeln
  • imperative API zum Einbetten von HTML-Fragmenten
  • möglichkeit, den Inhalt neu zu formatieren

Zusammenfassung:

Die Lösung von FakeRainBrigand hat für mich eine Weile ganz gut funktioniert, bis ich neue Probleme bekam. ContentEditables sind ein Schmerz, und es ist nicht einfach, mit React ...

Dies JSFiddle demonstriert das Problem.

Wie Sie sehen, wird der Inhalt nicht gelöscht, wenn Sie einige Zeichen eingeben und auf Clear klicken. Dies liegt daran, dass wir versuchen, den inhaltsbearbeitbaren Wert auf den letzten bekannten Wert für die virtuelle Domäne zurückzusetzen.

So scheint es, dass:

  • Sie benötigen shouldComponentUpdate, um Caret-Positionssprünge zu verhindern
  • Sie können sich nicht auf den VDOM-Algorithmus von React verlassen, wenn Sie shouldComponentUpdate auf diese Weise verwenden.

Sie benötigen also eine zusätzliche Zeile, damit Sie sicher sind, dass der DOM-Inhalt tatsächlich aktualisiert wird, wenn shouldComponentUpdate yes zurückgibt.

Die Version hier fügt also ein componentDidUpdate hinzu und wird zu:

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;
    }
});

Der Virtual Dom bleibt veraltet und es ist möglicherweise nicht der effizienteste Code, aber er funktioniert zumindest :) Mein Fehler ist behoben


Details:

1) Wenn Sie shouldComponentUpdate setzen, um Caret-Sprünge zu vermeiden, rendert das inhaltsbearbeitbare Objekt niemals (zumindest bei Tastendruck).

2) Wenn die Komponente bei einem Tastendruck nie neu gerendert wird, behält React eine veraltete virtuelle Domäne für diesen Inhalt bei.

3) Wenn React eine veraltete Version des inhaltsbearbeitbaren Objekts in seinem virtuellen Domänenbaum beibehält, wird beim Versuch, das inhaltsbearbeitbare Objekt auf den im virtuellen Domänen veralteten Wert zurückzusetzen, während des virtuellen Domänendifferentials React berechnet, dass es keine Änderungen für das DOM gibt!

Dies passiert meistens, wenn:

  • sie haben anfangs einen leeren Inhalt, der bearbeitet werden kann (shouldComponentUpdate = true, prop = "", previous vdom = N/A).
  • der Benutzer gibt Text ein und Sie verhindern das Rendern (shouldComponentUpdate = false, prop = text, previous vdom = "")
  • nachdem der Benutzer auf eine Überprüfungsschaltfläche geklickt hat, möchten Sie dieses Feld leeren (shouldComponentUpdate = false, prop = "", previous vdom = "").
  • da sowohl die neu produzierte als auch die alte Domäne "" sind, berührt React den Dom nicht.
61

Dies ist wahrscheinlich nicht genau die Antwort, nach der Sie suchen, aber da ich selbst damit zu kämpfen hatte und Probleme mit vorgeschlagenen Antworten hatte, entschloss ich mich, sie unkontrolliert zu machen.

Wenn editable prop false ist, verwende ich text prop so wie es ist, aber wenn es true ist, wechsle ich in den Bearbeitungsmodus, in dem text ] _ hat keine Auswirkung (aber zumindest flippt der Browser nicht aus). Während dieser Zeit werden onChange von der Steuerung abgefeuert. Wenn ich editable zurück zu false ändere, füllt es HTML mit allem, was in text übergeben wurde:

/** @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;
14
Dan Abramov

Ich schlage vor, einen mutationObserver zu verwenden, um dies zu tun. Es gibt Ihnen viel mehr Kontrolle darüber, was los ist. Außerdem erfahren Sie mehr darüber, wie das Durchsuchen alle Tastenanschläge interpretiert

Hier in 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;
    }
}
4
klugjo

Hier ist eine Komponente, die viel davon von Lovasoa beinhaltet: https://github.com/lovasoa/react-contenteditable/blob/master/index.js

Er stellt das Ereignis in den 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;
}

Ich benutze einen ähnlichen Ansatz erfolgreich

2
Jeff

Dies ist die einfachste Lösung, die für mich funktioniert hat.

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

Da nach Abschluss der Bearbeitung der Fokus des Elements immer verloren geht, können Sie einfach den onBlur-Hook verwenden.

<div onBlur={(e)=>{console.log(e.currentTarget.textContent)}} contentEditable suppressContentEditableWarning={true}>
     <p>Lorem ipsum dolor.</p>
</div>
1
Saint Laurent