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);
Edit:Sebastien Lorber's answer を参照してください。これは私の実装のバグを修正します。
OnInputイベントを使用し、オプションでonBlurをフォールバックとして使用します。余分なイベントを送信しないように、以前の内容を保存することをお勧めします。
私は個人的にこれをレンダリング機能として持っています。
var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);
return (<ContentEditable html={this.state.html} onChange={handleChange} />);
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
Edit 06/2016:ブラウザーが、あなたが与えたばかりのhtmlをブラウザーが「再フォーマット」しようとしたときに発生する新しい問題をちょうど発見しました。コンポーネントは常に再レンダリングされます。 参照
Edit 07/2016:ここに私の本番のcontentEditable実装があります。 react-contenteditable
には、次のような追加オプションがあります。
FakeRainBrigandのソリューションは、新しい問題が発生するまでしばらくの間、かなりうまく機能しました。 ContentEditablesは苦痛であり、Reactを扱うのは本当に簡単ではありません...
この JSFiddle は問題を示しています。
ご覧のとおり、いくつかの文字を入力してClear
をクリックしても、コンテンツはクリアされません。これは、contenteditableを最後の既知の仮想dom値にリセットしようとするためです。
そのため、次のように思われます。
shouldComponentUpdate
が必要ですshouldComponentUpdate
をこのように使用すると、ReactのVDOM差分アルゴリズムに依存できません。そのため、shouldComponentUpdate
がyesを返すたびに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;
}
});
Virtual domは古く、最も効率的なコードではないかもしれませんが、少なくとも動作します:) 私のバグは解決されました
詳細:
1)キャレットジャンプを回避するためにshouldComponentUpdateを配置すると、contenteditableは再描画されません(少なくともキーストロークで)
2)キーストロークでコンポーネントが再レンダリングされない場合、Reactはこのコンテンツの古い仮想domを編集可能にします。
3)Reactが仮想domツリーに古いバージョンのcontenteditableを保持している場合、仮想domでcontenteditableを古い値にリセットしようとすると、仮想dom diff中にReactは、DOMに適用する変更がないことを計算します!
これは主に次の場合に発生します。
これはおそらくあなたが探している答えではありませんが、自分でこれに苦労し、提案された答えに問題があるため、代わりに制御されないようにすることにしました。
editable
propがfalse
の場合、text
propをそのまま使用しますが、true
の場合は、text
が無効な編集モードに切り替えます。 (ただし、少なくともブラウザはフリークしません)。この間、onChange
はコントロールによって起動されます。最後に、editable
をfalse
に戻すと、text
で渡されたものでHTMLが埋められます。
/** @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;
これを行うには、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;
}
}
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;
}
私は同様のアプローチをうまく使っています
これは私にとって最も簡単な解決策です。
<div
contentEditable='true'
onInput = {e => {console.log('text of div', e.currentTarget.textContent)}}
>
Text in div
</div>
編集が完了すると、要素からのフォーカスは常に失われるため、単にonBlurフックを使用できます。
<div onBlur={(e)=>{console.log(e.currentTarget.textContent)}} contentEditable suppressContentEditableWarning={true}>
<p>Lorem ipsum dolor.</p>
</div>