Comment écouter un événement change pour le contrôle basé sur 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: Voir réponse de Sébastien Lorber qui corrige un bug dans mon implémentation.
Utilisez l'événement onInput et éventuellement onBlur comme solution de secours. Vous voudrez peut-être sauvegarder le contenu précédent pour empêcher l'envoi d'événements supplémentaires.
J'aurais personnellement cela comme fonction de rendu.
var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);
return (<ContentEditable html={this.state.html} onChange={handleChange} />);
Qui utilise ce simple wrapper autour de 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;
}
});
Éditer 2015
Quelqu'un a créé un projet sur NPM avec ma solution: https://github.com/lovasoa/react-contenteditable
Edit 06/2016: Je viens de rencontrer un nouveau problème qui se produit lorsque le navigateur essaie de "reformater" le code HTML que vous venez de lui donner, ce qui conduit à composant toujours re-rendu. Voir
Edit 07/2016: voici mon implémentation contentEditable de production. Il a quelques options supplémentaires sur react-contenteditable
que vous voudrez peut-être, y compris:
La solution de FakeRainBrigand a fonctionné assez bien pour moi pendant un certain temps, jusqu'à ce que je rencontre de nouveaux problèmes. ContentEditables sont une douleur, et ne sont pas vraiment faciles à traiter avec React ...
Ceci JSFiddle illustre le problème.
Comme vous pouvez le constater, lorsque vous tapez des caractères et cliquez sur Clear
, le contenu n’est pas effacé. En effet, nous essayons de réinitialiser la propriété contenteditable à la dernière valeur de dom virtuelle connue.
Il semble donc que:
shouldComponentUpdate
pour empêcher les sauts de position de curseurshouldComponentUpdate
de cette façon.Vous avez donc besoin d’une ligne supplémentaire pour que chaque fois que shouldComponentUpdate
renvoie oui, vous êtes sûr que le contenu du DOM est réellement mis à jour.
Donc la version ici ajoute un componentDidUpdate
et devient:
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;
}
});
Le dom virtuel reste obsolète, et ce n'est peut-être pas le code le plus efficace, mais au moins ça marche :) Mon bug est résol
Détails:
1) Si vous mettez devraitComponentUpdate pour éviter les sauts de curseur, alors le contenteditable ne redirige jamais (au moins sur les frappes au clavier)
2) Si le composant ne réapparaît jamais lors de la frappe d'une touche, alors React conserve un dom virtuel obsolète pour ce contenu modifiable.
3) Si React conserve une version obsolète de la propriété contenteditable dans son arborescence de dom virtuelle, si vous essayez de réinitialiser la propriété contenteditable à la valeur périmée du dom virtuel, alors, pendant le diff diff virtuel, React calculera qu'il n'y a aucune modification à appliquer au DOM!
Cela arrive surtout quand:
Ce n'est probablement pas exactement la réponse que vous recherchez, mais après avoir moi-même eu du mal à résoudre ce problème et avoir eu des problèmes avec les réponses suggérées, j'ai décidé de le rendre non contrôlé à la place.
Lorsque editable
prop est false
, j'utilise text
prop tel quel, mais lorsqu'il s'agit de true
, je passe en mode d'édition dans lequel text
n'a aucun effet (mais au moins le navigateur ne panique pas). Pendant ce temps, onChange
sont déclenchés par le contrôle. Enfin, lorsque je remplace editable
par false
, il remplit le code HTML avec tout ce qui a été passé dans 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;
Je suggère d'utiliser un mutationObserver pour le faire. Cela vous donne beaucoup plus de contrôle sur ce qui se passe. Il vous donne également plus de détails sur la façon dont le parcours interprète toutes les frappes.
Ici en 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;
}
}
Voici un composant qui intègre une grande partie de cela par lovasoa: https://github.com/lovasoa/react-contenteditable/blob/master/index.js
Il shims l'événement dans le 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;
}
J'utilise une approche similaire avec succès
C'est la solution la plus simple qui a fonctionné pour moi.
<div
contentEditable='true'
onInput = {e => {console.log('text of div', e.currentTarget.textContent)}}
>
Text in div
</div>
Comme lorsque la modification est terminée, la focalisation de l'élément est toujours perdue, vous pouvez simplement utiliser le crochet onBlur.
<div onBlur={(e)=>{console.log(e.currentTarget.textContent)}} contentEditable suppressContentEditableWarning={true}>
<p>Lorem ipsum dolor.</p>
</div>