Simple variable placeholders in Draft.js - reactjs

We're trying to implement variable placeholders for a feature in our app.
Essentially the user will create a template using a WYSIWYG editor and put variables into the text e.g:
Hello {{first_name}}
These variables will then be interpolated by our back end (e.g. swapping {{first_name}} with "Peter").
Our users are non-technical so we don't want them to have to add the placeholders manually. We want them to drag and drop the variables into their text from a list that we predefine.
Is there a simple way of doing this with DraftJS or CKEdior out of the box?

Thanks for A2A.
We had a similar feature requirement to add templates with placeholders for mailing.
Dear {{recipient.first_name}},
Mail Body Template
With best regards,
{{sender.first_name}} {{sender.last_name}}
Manager - ABC Company.
Here is how we implemented it. I hope this would be helpful.
draft-js provides a Modifier class for this very use-case.
Modifier API has insertText method to insert custom placeholder text in the content-{{first_name}} in your case.
import { EditorState, Modifier } from 'draft-js';
insertPlaceholder = placeholderText => {
const { editorState } = this.state;
const newContentState = Modifier.insertText(
editorState.getCurrentContent(), // get ContentState from EditorState
editorState.getSelection(),
placeholderText
);
this.setState({
editorState: EditorState.createWithContent(newContentState); // get EditorState with ContentState
});
}
Then add a button to trigger the insertPlaceholder method.
<button
onClick={() => this.insertPlaceholder('{{first_name}}')}
>
Add First Name
</button>
When clicked on Add First Name placeholder text will be inserted at the current cursor position.
If the editor is out of focus, the text will be inserted at the beginning.
For Reusability, you can create a custom plugin and include it in options.
Placeholder plugin example screenshot
Note:
If you have multiple placeholders, I would rather suggest using a select input with possible placeholder options - this will keep UI clean instead of having a bunch of buttons.
Any suggestions are welcome.

You can create a button as follows
<button
onMouseDown={e => this.onAddText(e, '{{first_name}}' )}
>
ADD Name
</button>
and inside onAddText function
import { insertText } from 'draft-js-modifiers';
onAddText = (e, text) => {
e.preventDefault();
const { editorState } = this.state;
const EditorStat = insertText(editorState, text);
this.setState({ editorState: EditorStat });
};
insertText is method provided by draft-js-modifiers
and then use this.state.editorState as follows:
import {
Editor,
} from 'draft-js';
<Editor
editorState={this.state.editorState}
/>

Related

React toggle hyperlink of grid header

I am working with react with Web API. I need one solution where I wanted to have one Hyperlink for once of the headers of Grid. And once I click's over the hyperlink, I wanted to hide this hyperlink and show other hyperlink (toggle). For Ex-
Those Hyperlinks are not directing anywhere. These are in use to select all Radiobuttons and another Hyperlink is used for unselecting all the Radiobuttons for Grid rows.
It's easy to toggle. But it's a hyperlink, which makes it a little tricky. Does the hyperlink links to anywhere?
Anyway, you can toggle the href in onClick, in order for 'href' take effect before 'href' itself is changed, use setTimeout (10 milliseconds should be enough):
import React from 'react'
const ToggleLinks = () => {
const google = {href:"https://google.ca", text:"google"};
const yahoo = {href:"https://yahoo.ca", text:"yahoo"};
const [link, setLink] = React.useState(google);
const toggleLink = e => {
console.log('clicked')
console.log(e.target)
setTimeout(() => {
setLink(p => {
return p.href === "https://google.ca" ? yahoo : google;
})
}, 10);
}
return (
<div>
<h1>ToggleLinks</h1>
{link.text}
</div>
)
}
export default ToggleLinks

Draft.js convertFromHtml, htmlToDraft and stateFromHTML ignores style attribute

I want to initialize a Draft.js text editor with the initial state. So I have this string:
const sampleMarkup = '<p>Given <span style="color: #2a00ff;"><strong>Name</strong></span></p>';
And I need colorful text.
I know that convertFromHtml, htmlToDraft, and stateFromHTML like do not accept this style attribute, so I found that I can use stateFromHTML with the second parameter options.
const options = {
customInlineFn: (element, { Style }) => {
if (element.style.color) {
return Style('color-' + element.style.color);
}
}
};
const content = stateFromHTML(sampleMarkup, options);
const [editorState, setEditorState] = useState(EditorState.createWithContent(
content
));
And I try to do this, but the text is still not colorful. Also, I try to change from
return Style('color-' + element.style.color);
to
return Style('CUSTOM_COLOR_' + element.style.color);
Didn't help.
Also, maybe there is another text editor for react, that I can use to work easier with HTML?
Thanks for any help :)

How can I grab the text value from content in tiptap react (typescript) wysiwyg?

I am trying to grab the text that is typed into the wysiwig to store it into my database.
I have tried a lot of different stuff and this was my most recent iteration:
<EditorContent editor={editor} className="contentInput" style={{ backgroundColor:'lightgrey' }} value={state.content} onChange={(evt) => setState({ ...state, content: evt.target.value })}/>
and with that I am getting the error that value is not a property on target. I believe this is because it is no longer an HTML input element, but I am not sure how I would now grab that content to put in the database?
const editor = useEditor({
// ...
onUpdate({ editor }) {
setState(editor.getJSON());
},
});
First you need to create a state with useState hook, then create the editor with useEditor Hook, inside it you're going to to add event onUpdate and set the value of the editorContent state on every update on the editor.
const [editorContent, setEditorContent] = useState("");
const editor = useEditor({
extensions: [StarterKit],
content: "<p>Hello World! 🌎️</p>",
onUpdate({ editor }) {
setEditorContent(editor.getHTML());
},
});
const msg = editor?.getText();
console.log("msg", msg);
In console you'll get the normal text which is written in tiptap input field.
Use optional chaining because editor initially return null and you might get error of not reading value of undefined.

Close detail panel and open another on material-table

I am displaying a table in the detail panel of another table using material-table. I want to close or remove the existing detail panel on clicking another row data expand icon and open the detail panel of that particular row. This is the codesandbox link im working on https://codesandbox.io/s/material-demo-forked-8zy6z?file=/demo.js:0-2696.
Note: I want to close first and then need to open the other row data detail panel
you can do it with useState
const [activePanel, setActivePanel] = React.useState(null)
now onClick of row you can do something like this
onClick={() => setActivePanel(item._id)}
now you know what is active item
{activePanel && <>your jsx code goes here</>}
You can use tableRef and change the dataManager detail panel type from multiple to single.
import {useRef } from "react";
const tableRef = useRef(0);
<MaterialTable
tableRef={tableRef}
detailPanel={(rowData) => {
if (tableRef.current.dataManager !== null) {
tableRef.current.dataManager.detailPanelType = "single";
}
return (
//your Table here
);
}}
/>
you can do this by adding the following prop in options prop of material-table
detailPanelType: "single",
e.g:
options:{{
detailPanelType: "single",
}}

Inheritance in react component

Please consider a following case:
I have created a "baseUser" component which is a form having three fields username, password and name. Now I want this form in three different applications : Application1, Application2 and Application3.
In Application1, User component should use this baseUser component but want only two fields (username, password) from baseUser state and should also have two additional fields which is first-name and last-name.
In Application2 and Application3, User component should work same as the baseUser.
Also all the actions, events, states should be able to work alone and be able to overridden.
The render method should also be overridden as their can be different UIs for different applications.
How can we achieve this functionality using react components? Does inheriting "baseUser" in applications using "extends" cause any issue (Or is it correct way to do it)?
React does not use Inheritance. You can't extend a react component. React basically work on the Composition. Composition means, creating small parts and combine them together to make a complete part.
Now, in your situation. There are only 3 input fields into your baseUsr component and you want to use only two of them into your application1. Simply you can't do this. You have to render complete baseUsr component.
You are getting the concept of components wrong. Think about components like a function or part of UI that is abstract or can be used in standalone.
For example, you can create a Header component because it can be used in isolation and can be used on multiple pages. But an input field in a form can not be used in isolation.
This confusion is created because you create components just like classes in javascript but they are the basic building block of UI.
For more information read Composition VS inheritance on React docs
You can write your baseUser this way:
class BaseUserForm extends Component {
handleChange = ({ currentTarget: input }) => {
const data = { ...this.state.data };
data[input.name] = input.value;
this.setState({ data }); //state is in inheriated form.
};
handleSubmit = e => {
e.preventDefault();
//Form validation
if (!errors) this.doSubmit();
};
renderInput = (name, label, type = "text") => {
return (
<Input
name={name}
label={label}
type={type}
error={this.state.errors[name]}
value={this.state.data[name]}
onChange={this.handleChange}
/>
);
};
renderButton = (label, type = "submit") => {
return (
<button
type={type}
className="btn btn-primary"
// disabled={this.validate()}
>
{label}
</button>
);
};
}
export default BaseUserForm;
Then depending on your Application, you can add/remove input fields as needed.
class AppForm extends BaseUserForm{
state={
data:{username:"",
password:""}
};
doSubmit = () => {
//Do your submit here.
};
render(){
return(
<form onSubmit={this.handleSubmit}>
{this.renderInput("username", "User Name")}
{this.renderInput("password", "Password")}
//Add more field as needed.
</form>
);
}
}

Resources