javascript - trigger - Evento React.js: onChange para contentEditable
react contenteditable (5)
Aquí hay un componente que incorpora mucho de esto por lovasoa: https://github.com/lovasoa/react-contenteditable/blob/master/index.js
Él calza el evento en el 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;
}
Estoy usando un enfoque similar con éxito
¿Cómo escucho para cambiar el evento por el control basado en el contenido?
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);
Probablemente esta no sea exactamente la respuesta que estás buscando, pero habiendo luchado con esto yo mismo y teniendo problemas con las respuestas sugeridas, decidí hacerlo sin control.
Cuando editable
prop es false
, uso text
Prop como está, pero cuando es true
, cambio al modo de edición en el que el text
no tiene ningún efecto (pero al menos el navegador no se vuelve loco). Durante este tiempo, onChange
se dispara por el control. Finalmente, cuando cambio editable
nuevo a false
, rellena HTML con lo que pasó en el 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;
Sugiero usar un mutationObserver para hacer esto. Te da mucho más control sobre lo que está sucediendo. También le brinda más detalles sobre cómo el navegador interpreta todas las pulsaciones de teclas
Aquí 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;
}
}
Editar 2015
Alguien ha realizado un proyecto sobre NPM con mi solución: https://github.com/lovasoa/react-contenteditable
Editar 06/2016: Acabo de encontrar un nuevo problema que ocurre cuando el navegador intenta "formatear" el html que acaba de darle, lo que hace que el componente siempre vuelva a generar. See
Edición 07/2016: aquí está mi contenido de producción. Implementación editable. Tiene algunas opciones adicionales sobre react-contenteditable
que pueda desear, incluyendo:
- cierre
- API imperativa que permite incrustar fragmentos html
- capacidad de reformatear el contenido
Resumen:
La solución de FakeRainBrigand me ha funcionado bastante bien hasta que tuve nuevos problemas. ContentEditables son un dolor, y no son realmente fáciles de tratar con Reaccionar ...
Este JSFiddle demuestra el problema.
Como puede ver, cuando escribe algunos caracteres y hace clic en Clear
, el contenido no se borra. Esto se debe a que tratamos de restablecer el valor dom virtual satisfecho con el último valor virtual conocido.
Entonces parece que:
- Necesitas
shouldComponentUpdate
para evitar saltos en la posición deshouldComponentUpdate
- No puede confiar en el algoritmo de diferenciación VDOM de React si usa
shouldComponentUpdate
esta manera.
Por lo tanto, necesita una línea adicional para que siempre que shouldComponentUpdate
devuelva yes, esté seguro de que el contenido de DOM se haya actualizado realmente.
Entonces, la versión aquí agrega un componentDidUpdate
y se convierte en:
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;
}
});
El Dom virtual permanece obsoleto, y puede que no sea el código más eficiente, pero al menos funciona :) Mi error está resuelto
Detalles:
1) Si pones shouldComponentUpdate para evitar saltos de caret, entonces el contenteditable nunca vuelve a soltar (al menos en las teclas)
2) Si el componente nunca se detiene en la pulsación de tecla, entonces React mantiene una dom virtual virtual obsoleta para este contenido.
3) Si React mantiene una versión desactualizada de contenteditable en su árbol dom virtual, entonces si intenta restablecer el contenteditable al valor desactualizado en el dom virtual, entonces durante el dom diff virtual, React calculará que no hay cambios en aplicar al DOM!
Esto sucede principalmente cuando:
- tienes un contenteditable vacío inicialmente (shouldComponentUpdate = true, prop = "", vdom anterior = N / A),
- el usuario escribe texto y previene las representaciones (shouldComponentUpdate = false, prop = text, vdom = "" anterior)
- después de que el usuario haga clic en un botón de validación, quiere vaciar ese campo (shouldComponentUpdate = false, prop = "", vdom previo = "")
- ya que tanto el vodium recién producido como el viejo son "", React no toca el dom.
Editar: Vea la respuesta de Sebastien Lorber que soluciona un error en mi implementación.
Use el evento onInput y, opcionalmente, onBlur como alternativa. Es posible que desee guardar los contenidos anteriores para evitar el envío de eventos adicionales.
Personalmente tendré esto como mi función de renderizado.
var handleChange = function(event){
this.setState({html: event.target.value});
}.bind(this);
return (<ContentEditable html={this.state.html} onChange={handleChange} />);
jsbin
Que usa esta envoltura simple alrededor de contenido Editable.
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;
}
});