React.js: contentEditable के लिए onChange इवेंट


120

मैं contentEditable-based नियंत्रण के लिए घटना को बदलने के लिए कैसे सुनूं?

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/


11
अपने आप से संघर्ष करते हुए, और सुझाए गए उत्तरों के साथ समस्या होने के बाद, मैंने इसके बजाय इसे अनियंत्रित करने का निर्णय लिया। है कि, मैं डाल initialValueमें stateऔर में इसका इस्तेमाल करते हैं render, लेकिन मैं न दें अद्यतन इसे आगे प्रतिक्रिया।
दान अब्रामोव

आपका JSField काम नहीं करता है
ग्रीन

मैंने contentEditableअपना दृष्टिकोण बदलकर संघर्ष करने से परहेज किया - spanया इसके बजाय paragraph, मैंने inputइसकी readonlyविशेषता के साथ उपयोग किया है ।
ओविडिउ-मिउ

जवाबों:


79

संपादित करें: सेबेस्टियन लॉबर का उत्तर देखें जो मेरे कार्यान्वयन में एक बग को ठीक करता है।


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

1
@NVI, यह shouldComponentUpdate विधि है। यह केवल तभी उछलेगा जब html प्रोप एलिमेंट में वास्तविक html के साथ सिंक से बाहर हो। जैसे अगर आपने कियाthis.setState({html: "something not in the editable div"}})
ब्रिगैंड

1
अच्छा है, लेकिन मैं कॉल का अनुमान लगाना this.getDOMNode().innerHTMLमें shouldComponentUpdateबहुत सही अनुकूल नहीं है
सेबेस्टियन Lorber

@ SebastienLorber बहुत अनुकूलित नहीं है , लेकिन मुझे पूरा यकीन है कि इसे सेट करने की तुलना में html को पढ़ना बेहतर है। एकमात्र अन्य विकल्प जो मैं सोच सकता हूं, वह सभी घटनाओं को सुनना है जो html को बदल सकते हैं, और जब ऐसा होता है तो आप HTML को कैश करते हैं। वह शायद ज्यादातर समय तेजी से होगा, लेकिन बहुत अधिक जटिलता जोड़ देगा। यह बहुत ही सुनिश्चित और सरल उपाय है।
ब्रिगैंड

3
यह वास्तव में थोड़ा त्रुटिपूर्ण है जब आप state.htmlअंतिम "ज्ञात" मान पर सेट करना चाहते हैं , तो रिएक्ट DOM को अपडेट नहीं करेगा क्योंकि नया html बिल्कुल वैसा ही है जैसा कि रिएक्ट का संबंध है (भले ही वास्तविक डोम अलग है)। Jsfiddle देखें । मुझे इसके लिए एक अच्छा समाधान नहीं मिला है, इसलिए किसी भी विचार का स्वागत है।
यूनीवरियो

1
@ डेडेस्ट shouldComponentUpdateशुद्ध होना चाहिए (इसके साइड इफेक्ट्स नहीं हैं)।
ब्रिगैंड

66

2015 को संपादित करें

किसी ने मेरे समाधान के साथ एनपीएम पर एक परियोजना बनाई है: https://github.com/lovasoa/react-contentitable

संपादित करें 06/2016: मैंने अभी एक नई समस्या का एनकाउंटर किया है, जब ब्राउज़र उस HTML को " रिफॉर्मेट " करने की कोशिश करता है, जिसे आपने अभी-अभी उसे दिया था, जिसके कारण घटक हमेशा री-रेंडर होता है। देख

07/2016 को संपादित करें: यहां मेरा प्रोडक्शन कंटेंटएडिटेबल इम्प्लीमेंटेशन है । इसके कुछ अतिरिक्त विकल्प हैं react-contenteditableजो आप चाहते हैं, जिनमें शामिल हैं:

  • ताला
  • HTML टुकड़े को एम्बेड करने की अनुमति अनिवार्य एपीआई
  • सामग्री को पुन: स्वरूपित करने की क्षमता

सारांश:

FakeRainBrigand के समाधान ने कुछ समय तक मेरे लिए काफी ठीक काम किया जब तक कि मुझे नई समस्याएं नहीं मिलीं। ContentEditables एक दर्द है, और वास्तव में React से निपटना आसान नहीं है ...

यह JSFiddle समस्या को प्रदर्शित करता है।

जैसा कि आप देख सकते हैं, जब आप कुछ अक्षर टाइप करते हैं और क्लिक करते हैं Clear , तो सामग्री साफ़ नहीं होती है। ऐसा इसलिए है क्योंकि हम अंतिम ज्ञात वर्चुअल डोम मूल्य के लिए संतोषप्रद को रीसेट करने का प्रयास करते हैं।

तो ऐसा लगता है कि:

  • आपको shouldComponentUpdateकैरेट पोजिशन जंप को रोकने की जरूरत है
  • यदि आप shouldComponentUpdateइस तरह से उपयोग करते हैं तो आप रिएक्ट के VDOM डिफरेंट एल्गोरिथ्म पर भरोसा नहीं कर सकते ।

इसलिए आपको एक अतिरिक्त लाइन की आवश्यकता है ताकि जब भी 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;
    }
});

वर्चुअल डोम पुराना हो गया है, और यह सबसे कुशल कोड नहीं हो सकता है, लेकिन कम से कम यह काम करता है :) मेरा बग हल हो गया है


विवरण:

1) यदि आप देखभाल कूद से बचने के लिए shouldComponentUpdate डालते हैं, तो संतोषी कभी रेंडरर्स (कम से कम कीस्ट्रोक्स पर) नहीं करेंगे

2) यदि घटक कभी भी कुंजी स्ट्रोक पर रेंडर नहीं करता है, तो रिएक्ट इस सामग्री के लिए एक पुराना वर्चुअल डोम रखता है।

3) यदि रिएक्ट अपने वर्चुअल डोम ट्री में संतुष्टी का पुराना संस्करण रखता है, तो यदि आप वर्चुअल डोम में पुरानी सामग्री को पुराना करने के लिए सामग्री को रीसेट करने का प्रयास करते हैं, तो वर्चुअल डोम के दौरान, रिएक्ट गणना करेगा कि कोई बदलाव नहीं हैं डोम पर लागू करें!

ऐसा अधिकतर तब होता है जब:

  • आपके पास प्रारंभ में एक खाली सामग्री है (shouldComponentUpdate = true, prop = "", पिछला vdom = N / a),
  • उपयोगकर्ता कुछ पाठ टाइप करता है और आप रेंडरिंग को रोकते हैं (shouldComponentUpdate = false, prop = text, पिछले vdom) "")
  • उपयोगकर्ता द्वारा सत्यापन बटन क्लिक करने के बाद, आप उस फ़ील्ड को खाली करना चाहते हैं (shouldComponentUpdate = false, prop = "", पिछला vdom = "")
  • जैसा कि नव निर्मित और पुरानी दोनों कविताएँ "" हैं, रिएक्ट डोम को स्पर्श नहीं करता है।

1
मैंने keyPress वर्जन लागू किया है, जो एंटर के दबाने पर टेक्स्ट को अलर्ट करता है। jsfiddle.net/kb3gN/11378
लुका कर्नलो

@LucaColonnello का आप बेहतर उपयोग करेंगे {...this.props}ताकि ग्राहक इस व्यवहार को बाहर से अनुकूलित कर सके
सेबस्टियन लॉबर

ओह, यह बेहतर है! ईमानदारी से मैंने यह समाधान केवल यह जांचने के लिए किया है कि क्या div पर काम करने वाला keyPress इवेंट है! स्पष्टीकरण के लिए धन्यवाद
लुका कर्नलो

1
क्या आप बता सकते हैं कि shouldComponentUpdateकोड कैरट जंप को कैसे रोकता है?
kmoe

1
@kmoe क्योंकि घटक कभी भी अद्यतन नहीं करता है यदि contentEditable में पहले से ही उपयुक्त पाठ है (अर्थात कीस्ट्रोक पर)। ContentEditable को React के साथ अपडेट करने से कैरेट जंप हो जाता है। सामग्री के बिना प्रयास करें और अपने आप को देखें;)
सेबस्टियन लॉर्बर

28

यह सबसे सरल समाधान है जो मेरे लिए काम करता है।

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

3
यह काम करने की जरूरत नहीं है, यह काम करता है! onInputउदाहरण में बताए अनुसार उपयोग करना याद रखें ।
सेबेस्टियन थॉमस

अच्छा और साफ, मुझे आशा है कि यह कई उपकरणों और ब्राउज़रों पर काम करता है!
JulienRioux

8
जब मैं रिएक्ट स्थिति के साथ पाठ को अपडेट करता हूं तो यह लगातार टेक्स्ट की शुरुआत की ओर बढ़ता है।
जुंटे

18

यह संभवत: वह उत्तर नहीं है, जिसकी आप तलाश कर रहे हैं, बल्कि स्वयं इससे जूझ रहे हैं और सुझाए गए उत्तरों के साथ समस्या है, मैंने इसके बजाय इसे अनियंत्रित करने का निर्णय लिया है।

जब 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;

क्या आप उन मुद्दों पर विस्तार कर सकते हैं जो आप अन्य उत्तरों के साथ कर रहे थे?
NV44

1
@NVI: मुझे इंजेक्शन से सुरक्षा की आवश्यकता है, इसलिए HTML को एक विकल्प के रूप में रखना एक विकल्प नहीं है। अगर मैं HTML नहीं डालता हूं और टेक्स्टकॉन्टेंट का उपयोग करता हूं, तो मुझे सभी प्रकार की ब्राउज़र असंगतताएं मिलती हैं और इसे shouldComponentUpdateइतनी आसानी से लागू नहीं किया जा सकता है, यहां तक ​​कि यह मुझे कैरट जंप से भी नहीं बचाता है। अंत में, मेरे पास CSS छद्म तत्व :empty:beforeप्लेसहोल्डर हैं लेकिन इस shouldComponentUpdateकार्यान्वयन ने FF और Safari को उपयोगकर्ता द्वारा साफ़ किए जाने पर फ़ील्ड को साफ़ करने से रोक दिया है। मुझे यह महसूस करने में 5 घंटे लगे कि मैं अनियंत्रित सीई के साथ इन सभी समस्याओं को दूर कर सकता हूं।
दान Abramov

मुझे समझ में नहीं आता कि यह कैसे काम करता है। आप में कभी नहीं बदलते editableहैं UncontrolledContentEditable। क्या आप एक उदाहरण देने योग्य उदाहरण प्रदान कर सकते हैं?
एनवीआई

@ एनवीआई: जब से मैं यहां एक रिएक्ट आंतरिक मॉड्यूल का उपयोग करता हूं, यह थोड़ा कठिन है .. मूल रूप से मैं editableबाहर से सेट करता हूं । उस फ़ील्ड के बारे में सोचें जो उपयोगकर्ता द्वारा "संपादित करें" दबाए जाने पर इनलाइन संपादित की जा सकती है और उपयोगकर्ता द्वारा "सहेजें" या "रद्द करें" दबाए जाने पर फिर से पढ़ना चाहिए। इसलिए जब यह आसानी से होता है, तो मैं प्रॉप्स का उपयोग करता हूं, लेकिन जब भी मैं "एडिट मोड" में प्रवेश करता हूं, तो उन्हें देखना बंद कर देता हूं और जब मैं बाहर निकलता हूं तो केवल प्रॉप्स को फिर से देखता हूं।
दान अब्रामोव

3
जिनके लिए आप इस कोड का उपयोग करने जा रहे हैं, रिएक्ट का नाम बदल दिया escapeTextForBrowserगया है escapeTextContentForBrowser
wuct

8

जब से सम्पादन पूरा होता है, तत्व से ध्यान हमेशा हट जाता है तो आप बस ऑन हुक का उपयोग कर सकते हैं।

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

5

मैं यह करने के लिए एक mutationObserver का उपयोग करने का सुझाव देता हूं। यह आपको बहुत अधिक नियंत्रण देता है कि क्या चल रहा है। यह आपको यह भी बताता है कि ब्राउज़ सभी कीस्ट्रोक्स की व्याख्या कैसे करता है

यहां टाइपस्क्रिप्ट में

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

2

यहाँ एक घटक है जो lovasoa द्वारा इसे शामिल करता है: https://github.com/lovasoa/react-contentitable/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;
}

मैं एक समान दृष्टिकोण का सफलतापूर्वक उपयोग कर रहा हूं


1
लेखक ने पैकेज में मेरे SO उत्तर का श्रेय दिया है। json। यह लगभग वही कोड है जो मैंने पोस्ट किया था और मैं पुष्टि करता हूं कि यह कोड मेरे लिए काम करता है। github.com/lovasoa/react-contentitable/blob/master/…
सेबस्टियन लोबेर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.