Create a numbered list within a textarea element in ReactJs - javascript

I am trying to create a text area element, where on load it should display a "1. ". The user can then type a sentence and hit return. Upon return it should render a "2." in the next line. When a user is on a line that has no text and clicks backspace, it should delete the number and return the focus to the previous number point. To illustrate this: User is on line "2." -> They press backspace which removes the "2." bullet point. -> returns them to the last character of line "1."
So far i have figured out this much:
const React = require('react');
const TextArea = React.createClass({
getInitialState: function() {
return {
textAreaVal: '1. '
};
},
editTextArea: function(value) {
this.setState({
textAreaVal: value
});
},
render: function() {
return (
<div className={"container"}>
<textarea autoFocus className={"proposal-textarea"} wrap="hard" defaultValue ={this.state.textAreaVal}
onChange={this.editTextArea} />
</div>
);
},
});
module.exports = TextArea;
Does anyone have any thoughts on the best way I can accomplish this?

What you're looking for is Reacts onKeyDown event.
Same way you have onChange set up, set up a function for onKeyDown that sends to this.handleKeyDown(event). Within that function, test event.charCode to determine which key was pressed (enter should be 13 and backspace should be 8), and then apply the necessary actions as needed.
EDIT: Moving my comment to the answer block;
To handle the incrementing number, simply add a secondary state element, lineNumber. Initialize it to 1 at start. Whenever you detect a keypress of Enter, increment lineNumber and append "\n" + this.state.lineNumber + ". " to your textAreaVal.

Well, look at this fiddle
const { Component, PropTypes } = React;
class NumberedTextArea extends Component {
constructor() {
super();
this._onKeyDown = this._onKeyDown.bind(this);
this.state = {
counter: 2,
text: `1. `
}
}
_onKeyDown(e) {
console.log(e.keyCode);
if (e.keyCode ===13) {
console.log(this.refs.text.value);
this.refs.text.value = `${this.refs.text.value}\n${this.state.counter++}. `;
e.preventDefault();
e.stopPropagation();
}
}
render() {
const style = {
height: 300,
width: 200
};
return (
<textarea ref="text" onKeyDown={this._onKeyDown} style={style}>
{this.state.text}
</textarea>
);
}
}
ReactDOM.render(
<NumberedTextArea />,
document.getElementById('root')
);

Related

How can I (and should I) dispatch click event in React.js

I have the following class component
...
constructor(props) {
super(props);
...
this.currentlyEditedInput = React.createRef()
}
...
onClick(id, column) {
return (event) => {
...
let { clientX, clientY } = event;
let repeatClick = ( true /*repeat needed*/) ? function() {
let click = new MouseEvent("click", {
clientX,
clientY
});
console.log(this.currentlyEditedInput.current.firstChild.tagName) // INPUT
console.log(this.currentlyEditedInput.current.firstChild.dispatchEvent) // function dispatchEvent()
this.currentlyEditedInput.current.firstChild.dispatchEvent(click) // nothing happens
} : undefined;
...
this.setState(/*new state*/, repeatClick); // when state is updated new input is rendered
...
}
}
...
render() {
...
return (
...
<TableCell
className={classes.cell}
key={column.name}
onClick={ this.onClick(row.id, column) }
>
...
<Input
ref={this.currentlyEditedInput}
autoFocus
...
/>
...
</TableCell>
...
)
}
When a table cell is clicked a new input with some value appears inside it, but the cursor is in the end of the input so the user has to click one more time. I want to make the cursor appear where the user clicks. So I dispatch the same click event in the callback (second argument of setState), but calling dispatchEvent does not seem to change anything.
May be this task should be solved in a completely different way. What is the correct way to do it in React?

Select a value from dropdown using React

I am making a very simple autocomplete section in a react application.
Code as follows,
index.js
import React from 'react';
import { render } from 'react-dom';
import Autocomplete from './Autocomplete';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'left',
};
const items = ['Moscow', 'Ufa', 'Tver', 'Alma ata'];
function onAutoCompleteHandle(val) {
alert(val);
}
const App = () => (
<div style={styles}>
<Autocomplete items={items} onAutocomplete={onAutoCompleteHandle.bind(this)}/>
</div>
);
render(<App />, document.getElementById('root'));
autocomplete.js
Render method:
const showSuggest = {
display: this.state.show ? 'block' : 'none',
}
return (
<div>
<input type="text"
className="autoCompleteInput"
onChange={this.handleChange}
ref={input => { this.textInput = input; }}
onClick={this.handleClick}
onKeyPress={this.handleKeyPress}
/>
<span className='suggestWrapper' style={showSuggest}>
<ul className='suggestAutocomplete'>
{this.state.items.map((item, i) => {
return
<li key={i} onClick={this.selectSuggestion}>
{item}
</li>
})}
</ul>
</span>
</div>
)
Complete working example: https://codesandbox.io/s/react-autocomplete-nbo3n
Steps to reproduce in the above given sandbox example:
-> Click on the input box.
-> Enter single alphabet eg.., a .
-> This gives the two items as result Ufa, Alma ata .
-> Press the down arrow key in keyboard.
As nothing happens here, unable to select any of the dropdown items.
As of now things work only if we move the mouse over the dropdown and select any item but I am in the need to implement the same behaviour for key down and enter.
Expected behaviour:
-> On keydown/keyup should be able to navigate the dropdown list items.
-> On click enter key on an item then that item should be the selected one.
I have tried assigning ref={input => { this.textInput = input; }} to the ul list items suggestAutocomplete but that also doesn't help..
Really I am stuck with this for very very long time. I humbly request you to consider this question.
It is also okay if you change this existing code, but I need to have both mouse selection and keyboard selection as well in the autocomplete..
Initialize a state with value -1
Add an event on keyDown
something like this:
handleKeyDown(e) {
if (e.keyCode == 40) { //down
this.setState({active: ++this.state.active})
} else if (e.keyCode == 38) { //up
this.setState({active: --this.state.active})
} else if (e.keyCode == 13) { //enter
e.preventDefault();
if (this.state.active > -1) {
this.selectSuggestion();
}
}
};
On click, reset the state to -1 again.
Check this: https://codesandbox.io/s/react-autocomplete-cf2yd

Textarea alike but for only one line

Something I like about the textarea element is that allows automatic spell checker. This is not happening with input text element. I need an element like textarea that will only show one line and never go to a next line even if the user press enter. I tried row='1' but doesn't matter if the user press enter the content moves to a next line. This could also be a react component. Exist something like that?
Like this:
document.querySelector('textarea').addEventListener('keydown', (e) => {
if (e.keyCode === 13) e.preventDefault();
});
textarea {
white-space: nowrap;
overflow:hidden;
}
<textarea rows="1"></textarea>
As your question tagged ReactJS
import React from 'react';
class App extends React.Component {
handleTextArea = (e) =>{
let lineCount = 0;
if (e.keyCode == 13) {
lineCount++;
}
if (lineCount >= 1) { // set here how may lines you want
e.preventDefault();
return false;
}
}
render() {
return (
<div>
<textarea onKeyDown={this.handleTextArea}>only one line</textarea>
</div>
)
}
}
export default App

Effective onBlur for react-data-grid

I'm jumping in on a pretty big React JS project which is using react-data-grid to display a bunch of editable data. Right now, you have to click an Update button to send changes to the server. My task at hand is to create auto-save functionality like so:
User selects cell to edit text
User changes text
User either moves to another cell or clicks away from data-grid
Changes are persisted to the server
Here's what I've tried:
onBlur event on each column. The event will fire, but it seems like the event was attached to a div and not the underlying input control. Therefore, I don't have access to the cell's values at the time this event is fired.
onCellDeselected on the <ReactDataGrid> component itself. It seems like this method is fired immediately upon render, and it only gets fired subsequent times when moving to another cell. If I'm editing the last cell and click away from the data-grid, this callback isn't fired.
Using react-data-grid, how can I effectively gain access to an editable cell's content when the user finishes editing?
The commits on react-data-grid are handled by the EditorContainer. The commit logic is simple. An editor commits a value when:
The editor unmounts
Enter is pressed
Tab is pressed
In some cases when the arrows are pressed (will skip this part is it may not be necessary for you, you can look at the logic for this on the EditorContainer)
Based on that the way I would recommend to do the autosave is:
Create an an EditorWrapper (HOC) the editors where you want auto save to be turned on
const editorWrapper(WrappedEditor) => {
return class EditorWrapper extends Component {
constructor(props) {
base(props);
this._changeCommitted = false;
this.handleKeyDown.bind(this);
}
handleKeyDown({ key, stopPropagation }) {
if (key === 'Tab' || key === 'Enter') {
stopPropagation();
this.save();
this.props.onCommit({ key });
this._changeCommitted = true;
}
// If you need the logic for the arrows too, check the editorContainer
}
save() {
// Save logic.
}
hasEscapeBeenPressed() {
let pressed = false;
let escapeKey = 27;
if (window.event) {
if (window.event.keyCode === escapeKey) {
pressed = true;
} else if (window.event.which === escapeKey) {
pressed = true;
}
}
return pressed;
}
componentWillUnmount() {
if (!this._changeCommitted && !this.hasEscapeBeenPressed()) {
this.save();
}
}
render() {
return (
<div onKeyDown={this.handleKeyDown}>
<WrappedComponent {...this.props} />
</div>);
}
}
}
When exporting you editor just wrap them with the EditorWrapper
const Editor = ({ name }) => <div>{ name }</div>
export default EditorWrapper(Editor);
Use one of the start or stop event callback handlers at the DataGrid level like onCellEditCommit
<DataGrid
onCellEditCommit={({ id, field, value }, event) => {
...
}
/>
or a valueSetter for a single the column definition:
const columns: GridColDef[] = [
{
valueSetter: (params: GridValueSetterParams) => {
// params.row contains the current row model
// params.value contains the entered value
},
},
];
<DataGrid columns={columns} />

Change the cursor position in a textarea with React

I have a textarea in React that I want to turn into a "notepad". Which means I want the "tab" key to indent instead of unfocus. I looked at this answer, but I can't get it to work with React. Here is my code:
handleKeyDown(event) {
if (event.keyCode === 9) { // tab was pressed
event.preventDefault();
var val = this.state.scriptString,
start = event.target.selectionStart,
end = event.target.selectionEnd;
this.setState({"scriptString": val.substring(0, start) + '\t' + val.substring(end)});
// This line doesn't work. The caret position is always at the end of the line
this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1;
}
}
onScriptChange(event) {
this.setState({scriptString: event.target.value});
}
render() {
return (
<textarea rows="30" cols="100"
ref="input"
onKeyDown={this.handleKeyDown.bind(this)}
onChange={this.onScriptChange.bind(this)}
value={this.state.scriptString}/>
)
}
When I run this code, even if I press the "tab" key in the middle of the string, my cursor always appears at the end of the string instead. Anyone knows how to correctly set the cursor position?
You have to change the cursor position after the state has been updated(setState() does not immediately mutate this.state)
In order to do that, you have to wrap this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1; in a function and pass it as the second argument to setState (callback).
handleKeyDown(event) {
if (event.keyCode === 9) { // tab was pressed
event.preventDefault();
var val = this.state.scriptString,
start = event.target.selectionStart,
end = event.target.selectionEnd;
this.setState(
{
"scriptString": val.substring(0, start) + '\t' + val.substring(end)
},
() => {
this.refs.input.selectionStart = this.refs.input.selectionEnd = start + 1
});
}
}
jsfiddle
For anyone looking for a quick React Hooks (16.8+) cursor position example:
import React, { useRef } from 'react';
export default () => {
const textareaRef = useRef();
const cursorPosition = 0;
return <textarea
ref={textareaRef}
onBlur={() => textareaRef.current.setSelectionRange(cursorPosition, cursorPosition)}
/>
}
In this example, setSelectionRange is used to set the cursor position to the value of cursorPosition when the input is no longer focused.
For more information about useRef, you can refer to React's official doc's Hook Part.
Here's a solution in a hooks-style architecture. My recommendation is to change the textarea value and selectionStart immediately on tab insertion.
import React, { useRef } from "react"
const CodeTextArea = ({ onChange, value, error }) => {
const textArea = useRef()
return (
<textarea
ref={textArea}
onKeyDown={e => {
if (e.key === "Tab") {
e.preventDefault()
const { selectionStart, selectionEnd } = e.target
const newValue =
value.substring(0, selectionStart) +
" " +
value.substring(selectionEnd)
onChange(newValue)
if (textArea.current) {
textArea.current.value = newValue
textArea.current.selectionStart = textArea.current.selectionEnd =
selectionStart + 2
}
}
}}
onChange={e => onChange(e.target.value)}
value={value}
/>
)
}
In React 15 best option is something like that:
class CursorForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleChange = event => {
// Custom set cursor on zero text position in input text field
event.target.selectionStart = 0
event.target.selectionEnd = 0
this.setState({value: event.target.value})
}
render () {
return (
<form>
<input type="text" value={this.state.value} onChange={this.handleChange} />
</form>
)
}
}
You can get full control of cursor position by event.target.selectionStart and event.target.selectionEnd values without any access to real DOM tree.

Categories

Resources