Node, Express, Sequelize, PostgreSQL, and React App Part 3 — React ContentEditable and Mic
This week I added React-ContentEditable and React-Mic to my project, as well as changing the structure of my Content components. Here is what we have so far:
Ideas can be created and there are CreateContent or EditContent forms that appear when an idea is clicked. Audio is recorded and the wavelengths of the audio are displayed with the black line in the pink rectangle. Audio can be saved to the computer.
React-ContentEditable
The React-ContentEditable form is a React component for a div with editable contents. Whatever is typed into the form is posting a text string and its HTML to the Contents table.
I added an html column with this Sequelize command:
sequelize migration:create --name add_html_to_contents
The migration looks like this:
'use strict';module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'Contents',
'html',
Sequelize.STRING
);
},down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'Contents',
'html'
);
}
};
Make sure that you add the new columns to the models and controllers as well.
sequelize db:migrate
Users can apply HTML tags to what is typed and it will be saved to the database.
import React from "react";
import ContentEditable from "react-contenteditable";
import sanitizeHtml from "sanitize-html";class ContentForm extends React.Component {state = {
idea_id: null,
post: '',
audio: '',
html: `<p>Hello <b>World</b> !</p><p>Paragraph 2</p>`,
editable: true
};handleChange = e => {
this.setState({ html: e.target.value, post: e.target.value.replace(/<[^>]+>/g, '') });
};sanitizeConf = {
allowedTags: ["b", "i", "em", "strong", "a", "p", "h1"],
allowedAttributes: { a: ["href"] }
};sanitize = () => {
this.setState({ html: sanitizeHtml(this.state.html, this.sanitizeConf) });
};handleSubmit = e => {
e.preventDefault();
this.props.postContent({
idea_id: this.props.idea.id,
post: this.state.post,
audio: this.state.audio,
html: this.state.html
})
};render() {
return (
<div className="contentForm">
<EditButton cmd="italic" />
<EditButton cmd="bold" />
<EditButton cmd="formatBlock" arg="h1" name="heading" />
<EditButton
cmd="createLink"
arg="https://github.com/lovasoa/react-contenteditable"
name="hyperlink"
/>
<form onSubmit={this.handleSubmit} action="">
<div>
<ContentEditable
className="editable"
html={this.state.html} // innerHTML of the editable div
disabled={!this.state.editable} // use true to disable edition
onChange={this.handleChange} // handle innerHTML change
onBlur={this.sanitize}
value={this.state.post}
/>
<br/>
<button type="submit">
Create Content
</button>
</div>
</form>
</div>
)
}
}function EditButton(props) {
return (
<button
key={props.cmd}
onMouseDown={evt => {
evt.preventDefault(); // Avoids losing focus from the editable area
document.execCommand(props.cmd, false, props.arg); // Send the command to the browser
}}
>
{props.name || props.cmd}
</button>
);
}export default ContentForm
I used Markup from the Interweave component to display the HTML in the correct format in the Content component.
import React from "react";
import { Markup } from 'interweave';
import Audio from './Audio.js';class Content extends React.Component {render() {
return (
<div className="content">
<Markup content={this.props.content.html} />
<Audio />
</div>
)
}
}
export default Content
React-Mic
React-Mic is component that lets users record audio from their computer and save it to their computer. I used MaterialUI to create the buttons for starting and stopping the recording. The audio is saved as WebM audio file format on your computer and is a BLOB in React. A Binary Large OBject (BLOB) is a collection of binary data stored as a single entity in a database management system. I will discuss how to store this in a future installment.
import React, {Component} from 'react';
import { render } from 'react-dom';
import { FloatingActionButton,
MuiThemeProvider } from 'material-ui';
import MicrophoneOn from 'material-ui/svg-icons/av/mic';
import MicrophoneOff from 'material-ui/svg-icons/av/stop';import { ReactMic, saveRecording } from 'react-mic';
import ReactGA from 'react-ga';ReactGA.initialize('UA-98862819-1');class Audio extends React.Component {
constructor(props){
super(props);
this.state = {
blobObject: null,
isRecording: false
}
}componentDidMount() {
ReactGA.pageview(window.location.pathname);
}startRecording= () => {
this.setState({
isRecording: true
});
}stopRecording= () => {
this.setState({
isRecording: false
});
}onSave=(blobObject) => {
}onStart=() => {
console.log('You can tap into the onStart callback');
}onStop= (blobObject) => {
this.setState({
blobURL : blobObject.blobURL
});
}onData(recordedBlob){
console.log('chunk of real-time data is: ', recordedBlob);
}render() {
const { isRecording } = this.state;return(
<MuiThemeProvider>
<div>
<ReactMic
className="oscilloscope"
record={isRecording}
backgroundColor="#FF4081"
visualSetting="sinewave"
audioBitsPerSecond= {128000}
onStop={this.onStop}
onStart={this.onStart}
onSave={this.onSave}
onData={this.onData}
strokeColor="#000000" />
<div>
<audio ref="audioSource" controls="controls" src={this.state.blobURL}></audio>
</div>
<br />
<FloatingActionButton
className="btn"
secondary={true}
disabled={isRecording}
onClick={this.startRecording}>
<MicrophoneOn />
</FloatingActionButton>
<FloatingActionButton
className="btn"
secondary={true}
disabled={!isRecording}
onClick={this.stopRecording}>
<MicrophoneOff />
</FloatingActionButton>
</div>
</MuiThemeProvider>
);
}
}export default Audio
See you in the next part!