DraftJs creating simplest possible custom block - javascript

what would be the easiest way to implement custom block in Draft?
At the moment I'm using this function for default blocks
editorToggleBlockType = (blockType) => {
this.onChange(
RichUtils.toggleBlockType(
this.state.editorState,
blockType
)
);
}
then I can apply custom class using blockStyler
blockStyler = (block) => {
if (block.getType() === 'unstyled') {
return 'paragraph';
} else {
return `custom-${block.getType()}`;
}
}
Sadly blockType accepts only default types like blockquote, ol, code-block etc. and on custom type gives me an error.
Uncaught TypeError: Cannot read property 'wrapper' of undefined
My question is - how to force editor to accept custom block types so I can apply className to them? Thank you.

You need to define it in blockRenderMap.
From the docs:
const blockRenderMap = Immutable.Map({
'atomic': {
// the docs use 'MyCustomBlock', but I changed it to 'atomic' to make it easier to follow.
// element is used during paste or html conversion to auto match your component;
// it is also retained as part of this.props.children and not stripped out
element: 'section',
wrapper: <MyCustomBlock {...this.props} />
}
});
// Include 'paragraph' as a valid block and updated the unstyled element but
// keep support for other draft default block types
const extendedBlockRenderMap = Draft.DefaultDraftBlockRenderMap.merge(blockRenderMap);
class RichEditor extends React.Component {
render() {
return (
<Editor
...
blockRenderMap={extendedBlockRenderMap}
/>
);
}
}
Rather confusingly, all this does is wrap your custom block in whatever is specified in the wrapper key. The actual block is then rendered by blockRendererFn, as in the docs:
function myBlockRenderer(contentBlock) {
const type = contentBlock.getType();
if (type === 'atomic') {
return {
component: MediaComponent,
editable: false,
props: {
foo: 'bar',
},
};
}
}
// Then...
import {Editor} from 'draft-js';
class EditorWithMedia extends React.Component {
...
render() {
return <Editor ... blockRendererFn={myBlockRenderer} />;
}
}
If we follow this example verbatim, you'd get a block that looked something like:
...
<MyCustomBlock>
<MediaComponent />
</MyCustomBlock>
...
And your className from blockStyleFn would get passed to MyCustomBlock, so you can pass it down to whichever native DOM node you like. That's also why you were getting the TypeError -- DraftJS couldn't find your custom block in blockRenderMap!
I hope this answers your question. DraftJS can be confusing, but it's a very powerful framework for building RTEs.

Related

array of html elements or strings typescript [duplicate]

Using React with TypeScript, there are several ways to define the type of children, like setting it to JSX.Element or React.ReactChild or extending PropsWithChildren. But doing so, is it possible to further limit which particular element that React child can be?
function ListItem() {
return (
<li>A list item<li>
);
}
//--------------------
interface ListProps {
children: React.ReactChild | React.ReactChild[]
}
function List(props: ListProps) {
return (
<ul>
{props.children} // what if I only want to allow elements of type ListItem here?
</ul>
);
}
Given the above scenario, can List be set up in such a way that it only allows children of type ListItem? Something akin to the following (invalid) code:
interface ListProps {
children: React.ReactChild<ListItem> | React.ReactChild<ListItem>[]
}
You can't constrain react children like this.
Any react functional component is just a function that has a specific props type and returns JSX.Element. This means that if you render the component before you pass it a child, then react has no idea what generated that JSX at all, and just passes it along.
And problem is that you render the component with the <MyComponent> syntax. So after that point, it's just a generic tree of JSX nodes.
This sounds a little like an XY problem, however. Typically if you need this, there's a better way to design your api.
Instead, you could make and items prop on List which takes an array of objects that will get passed as props to ListItem inside the List component.
For example:
function ListItem({ children }: { children: React.ReactNode }) {
return (
<li>{children}</li>
);
}
function List(props: { items: string[] }) {
return (
<ul>
{props.items.map((item) => <ListItem>{item}</ListItem> )}
</ul>
);
}
const good = <List items={['a', 'b', 'c']} />
In this example, you're just typing props, and List knows how to generate its own children.
Playground
Here's a barebones example that I am using for a "wizard" with multiple steps. It uses a primary component WizardSteps (plural) and a sub-component WizardStep (singular), which has a "label" property that is rendered in the main WizardSteps component. The key in making this work correctly is the Children.map(...) call, which ensures that React treats "children" as an array, and also allows Typescript and your IDE to work correctly.
const WizardSteps: FunctionComponent<WizardStepsProps> & WizardSubComponents = ({children}) => {
const steps = Children.map(children, child => child); /* Treat as array with requisite type */
return (
<div className="WizardSteps">
<header>
<!-- Note the use of step.props.label, which is properly typecast -->
{steps.map(step => <div className="WizardSteps__step">{step.props.label}</div>)}
</header>
<main>
<!-- Here you can render the body of each WizardStep child component -->
{steps.map(step => <div className="WizardSteps__body">{step}</div>)}
</main>
</div>
);
}
const Step: FunctionComponent<WizardStepProp> = ({label, onClick}) => {
return <span className="WizardSteps__label">
{label}
</span>
}
WizardSteps.Step = Step;
type WizardSubComponents = {
Step: FunctionComponent<WizardStepProp>
}
type WizardStepsProps = {
children: ReactElement<WizardStepProp> | Array<ReactElement<WizardStepProp>>
};
type WizardStepProp = {
label: string
onClick?: string
children?: ReactNode
}
Absolutely. You just need to use React.ReactElement for the proper generics.
interface ListItemProps {
text: string
}
interface ListProps {
children: React.ReactElement<ListItemProps> | React.ReactElement<ListItemProps>[];
}
Edit - I've created an example CodeSandbox for you:
https://codesandbox.io/s/hardcore-cannon-16kjo?file=/src/App.tsx

Switch the language in React without using external libraries

What ways to change language in React can you suggest without using external libraries? My way is to use the ternary operator {language === 'en'? 'title': 'titre'}. If language is en, displaytitle if not, display titre. What other way can you recommend. For example, that the translations should be placed in a separate json file.
Code here: https://stackblitz.com/edit/react-eu9myn
class App extends Component {
constructor() {
super();
this.state = {
language: 'en'
};
}
changeLanguage = () => {
this.setState({
language: 'fr'
})
}
render() {
const {language} = this.state;
return (
<div>
<p>
{language === 'en' ? 'title' : 'titre'}
</p>
<button onClick={this.changeLanguage}>change language</button>
</div>
);
}
}
Internationalization (i18n) is a hard problem with a few existing, standard solutions designed by expert translators and linguists to account for the breadth of language quirks across the world. You shouldn't generally try to come up with your own solution, even when you are fluent in all target languages.
That doesn't mean you need a library (you could implement one of those standards yourself) but writing the i18n logic inline will not scale and probably won't work well.
The easiest case of i18n is if you're translating strings that do not depend on context and are complete sentences with no interpolation. You could get away with a very basic approach there, like using a big dictionary of translations and just looking up each string in it. It would look sort of like your ternary but at least it would scale for many languages, and it would be reasonable to do that with no library:
l10n = {
'title': {en: 'title', fr: 'titre'}
}
<p>
{l10n['title'][lang]}
</p>
However, if there is going to be string interpolation in your website/application/whatever, please consider a library that implements, say, ICU.
Now, let me show you why it would be a bad idea. Suppose you have the string "you can see (n) rocks" where you want to replace (n) with an actual number, and you want the sentence to be grammatically correct so you need to compute number agreement, right ? so, "0 rocks", "1 rock", "2+ rocks"… looks like English plural is just adding an "s" (not true, but let's assume for now), you could implement that with ternaries. I see you used French in your example so, how about that ? "0 cailloux", "1 caillou", "2+ cailloux". Right, there are multiple plural forms in French. How do you write your code to account for that ? And what if you need a German translation ? maybe the translator will decide that the object should go first in the sentence, rather than last. How does your code handle word order based on language ?
All these problems should be delegated to the translator who encodes them into an ICU string, which is then evaluated by some code given a context to get a correct translation. Whether you use a library or implement it yourself, what you want in the end is some function — let's call it localize(string, context) that is pretty much independent from React and that you use in your components like this:
import localize from './somewhere'
<p>
{localize('title')}
</p>
If you really want to, you can pass the locale as an argument and have it stored in React's state somehow. This library decided it wasn't necessary because real users rarely switch language and it's OK to reload the whole application when that happens.
I just implemented a simple language component for work that uses a Localisation context/provider and a dictionary (e.g JSON). I'll go through the steps, and there's a workable codesandbox example at the end. This is a very basic approach, but it works well for us at the moment.
The example has:
1) A simple "dictionary" that contains the tokens you want to translate in each language defined by a short code
{ EN: { welcome: 'Welcome' }, FR: { welcome: 'Bienvenue' }, IT: { welcome: 'Benvenuto' } };
2) An initial state and reducer that you can update when the language changes
export const initialState = {
defaultLanguage: 'EN',
selectedLanguage: 'IT'
}
export function reducer(state, action) {
const { type, payload } = action;
switch (type) {
case 'LANGUAGE_UPDATE': {
return { ...state, selectedLanguage: payload };
}
default: return state;
}
}
3) A Localisation Context/Provider. You can wrap your code in the provider and every child component can get access to the state through the context. We import the dictionary and state/reducer, create the new context and then set up the provider into which we pass the state and dictionary.
import dictionary from './dictionary';
import { initialState, reducer } from './localisationReducer';
export const LocalisationContext = React.createContext();
export function LocalisationProvider({ children }) {
const localisationStore = useReducer(reducer, initialState);
return (
<LocalisationContext.Provider value={{ localisationStore, dictionary }}>
{children}
</LocalisationContext.Provider>
);
}
4) An example app. You can see the LocalisationProvider wrapping the other elements, but also a dropdown, and a component called Translate. I'll describe those next.
<LocalisationProvider>
<Dropdown />
<div className="App">
<h1>
<Translate token="welcome" />
</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
</LocalisationProvider>
5) The dropdown accesses the Localisation context and builds a dropdown with the languages. The key part is the handleSelected function which uses the dispatch from the localisation store to change the state (update the language):
import { LocalisationContext } from './localisation';
const langs = [
{ shortCode: 'EN', label: 'English' },
{ shortCode: 'FR', label: 'Français' },
{ shortCode: 'IT', label: 'Italiano' }
];
export function Dropdown() {
const {
localisationStore: [ state, dispatch ]
} = useContext(LocalisationContext);
const { selectedLanguage } = state;
const handleSelected = (e) => {
const { target: { value } } = e;
dispatch({ type: 'LANGUAGE_UPDATE', payload: value });
}
function getOptions(langs, selectedLanguage) {
return langs.map(({ shortCode, label }) => {
return <option value={shortCode}>{label}</option>
});
}
return (
<select onChange={handleSelected}>
{getOptions(langs, selectedLanguage)}
</select>
);
}
6) The Translate component which also accesses the state and dictionary through the context, and performs the translation based on the selected language.
import { LocalisationContext } from './localisation';
export default function Translator({ token }) {
const {
localisationStore: [state], dictionary
} = useContext(LocalisationContext);
const {
selectedLanguage, defaultLanguage
} = state;
const translatedToken = dictionary[selectedLanguage][token] || dictionary[defaultLanguage][token];
return (
<Fragment>
{translatedToken}
</Fragment>
);
}
Here's the codesandbox example for you to explore. Just select a new language from the dropdown to see the main "welcome" text change.

Setting up a utility class and using it within a vue component

A vue application I am working on currently has lots of code redundancies relating to date functions. In an effort to reduce these redundancies, I'd like to create a utility class as shown below, import it and set it to a Vue data property within the component, so I can call the date functions within it.
I am not certain on the best way to implement this. The current implementation results in an error saying TypeError: this.dates is undefined and my goal is not only to resolve this error but create/utilize the class in the Vue environment using best standards.
Importing utility class
import Dates from "./utility/Dates";
...
Component
const contactEditView = Vue.component('contact-edit-view', {
data() {
return {
contact: this.myContact
dates: Dates
}
},
...
Dates.js
export default {
dateSmall(date) {
return moment(date).format('L');
},
dateMedium(date) {
return moment(date).format('lll');
},
dateLarge(date) {
return moment(date).format('LLL');
}
};
View
Date of Birth: {{ dates.dateMedium(contact.dob) }}
My suggestion for this is to use a plugin option in Vue. About Vue plugin
So you will crate a new folder called services, add file yourCustomDateFormater.js:
const dateFormater = {}
dateFormater.install = function (Vue, options) {
Vue.prototype.$dateSmall = (value) => {
return moment(date).format('L')
}
Vue.prototype.$dateMedium = (value) => {
return moment(date).format('lll')
}
}
In main.js:
import YourCustomDateFormater from './services/yourCustomDateFormater'
Vue.use(YourCustomDateFormater)
And you can use it anywhere, like this:
this.$dateSmall(yourValue)
Or, if you want to use mixin. Read more about mixin
Create a new file dateFormater.js
export default {
methods: {
callMethod () {
console.log('my method')
}
}
}
Your component:
import dateFormater from '../services/dateFormater'
export default {
mixins: [dateFormater],
mounted () {
this.callMethod() // Call your function
}
}
Note: "Use global mixins sparsely and carefully, because it affects every single Vue instance created, including third party components. In most cases, you should only use it for custom option handling like demonstrated in the example above. It’s also a good idea to ship them as Plugins to avoid duplicate application." - Vue documentation
dateUtilsjs
import moment from 'moment-timezone'
function formatDateTime(date) {
return moment.utc(date).format("M/D/yyyy h:mm A")
}
export { formatDateTime }
Component JS
...
import { formatDateTime } from '../utils/dateUtils'
...
methods: {
formatDateTime,
}
Used within component
{{ formatDateTime(date) }}

Base questions with Serenity/JS?

I have just started looking into switching to Serenity/JS and wanted to know if it's best practice to have base Questions/Tasks?
There are many times I will want to check if a field is blank or has an error, so I created a 'base Question' to achieve this:
Base Question
import { Is, See, Target, Task, Wait, Value, Attribute } from 'serenity-js/lib/screenplay-protractor';
import { equals, contains } from '../../../support/chai-wrapper';
import { blank } from '../../../data/blanks';
export class InputFieldQuestion {
constructor(private inputField: Target) { }
isBlank = () => Task.where(`{0} ensures the ${this.inputField} is blank`,
Wait.until(this.inputField, Is.visible()),
See.if(Value.of(this.inputField), equals(blank))
)
hasAnError = () => Task.where(`{0} ensures the ${this.inputField} has an error`,
See.if(Attribute.of(this.inputField).called('class'), contains('ng-invalid'))
)
}
I then create classes specific to the scenario but simply extend the base question:
import { LoginForm } from '../scenery/login_form';
import { InputFieldQuestion } from './common';
class CheckIfTheUsernameFieldQuestion extends InputFieldQuestion {
constructor() { super(LoginForm.Username_Field) }
}
export let CheckIfTheUsernameField = new CheckIfTheUsernameFieldQuestion();
The beauty of node exports allow me to export an instantiated question class for use in my spec.
I just wanted to know if I am abusing the Serenity/JS framework or if this is okay? I want to establish a good framework and wanted to ensure I am doing everything to best practice. Any feedback is appreciated!
Sure, you could do it this way, although I'd personally favour composition over inheritance.
From what I can tell, you're designing standardised verification Tasks, which you want to use as follows:
actor.attemptsTo(
CheckIfTheUsernameField.isBlank()
)
You could accomplish the same result with a slightly more flexible design where a task can be parametrised with the field to be checked:
actor.attemptsTo(
EnsureBlank(LoginForm.Username_Field)
)
Where:
const EnsureBlank = (field: Target) => Task.where(`#actor ensures that the ${field} is blank`,
Wait.until(field, Is.visible()),
See.if(Value.of(field), equals(blank)),
);
Or, to follow the DSL you wanted:
const EnsureThat = (field: Target) => ({
isBlank: () => Task.where(`#actor ensures that the ${field} is empty`,
Wait.until(field, Is.visible()),
See.if(Value.of(field), equals(blank)),
),
hasError: () => Task.where(`#actor ensures that the ${field} has an error`,
See.if(Attribute.of(this.inputField).called('class'), contains('ng-invalid')),
),
});
Which can be used as follows:
actor.attemptsTo(
EnsureThat(LoginForm.Username_Field).isBlank()
);
Hope this helps!
Jan

ReactDOM render fails when using JSX

I am using ReactDOM.render() to render a component I have created. The component is fairly complicated, due to the specifics of the implementation, but I can easily render it iff I avoid using JSX syntax. However, if I use JSX, I cannot render the component at all and I get the following error:
TypeError: _this2.props.children.forEach is not a function
My code can be seen below (I also get a few warnings that I haven't gotten around to fixing yet, so you can just ignore those for the time being). Bear in mind that the structure of the HTML for the component is very strict (due to the CSS framework I'm using) and cannot be changed. Is there a way to use JSX for achieving the same result and, if so, what is it that I'm doing wrong?
// This function was based on this answer: https://stackoverflow.com/a/10734934/1650200
function generateUniqueId() {
// always start with a letter (for DOM friendlyness)
var idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65));
do {
// between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
var ascicode = Math.floor((Math.random() * 42) + 48);
if (ascicode < 58 || ascicode > 64) {
// exclude all chars between : (58) and # (64)
idstr += String.fromCharCode(ascicode);
}
} while (idstr.length < 32);
return (idstr);
}
// Technically this is not exactly a component, but I use it as such to make things simpler.
class Tab extends React.Component {
render() {
return React.createElement('div', {}, this.props.children);
}
}
// This is my Tabs component
class Tabs extends React.Component {
// In the constructor, I take all children passed to the component
// and push them to state with the necessary changes made to them.
constructor(props) {
super(props);
var state = {
group: 'tab_group_' + generateUniqueId(),
children: []
}
this.props.children.forEach(
function(child) {
if (!child instanceof Tab) {
throw "All children of a 'Tabs' component need to be of type 'Tab'. Expected type: 'Tab' Found Type: '" + child.class + "'";
return;
}
var tab = Object.assign({}, child);
tab.internalId = 'tab_' + generateUniqueId();
state.children.push(tab);
}
);
this.state = state;
}
// When rendering, I don't render the children as needed, but I create
// the structure I need to use for the final result.
render() {
var childrenToRender = [];
var groupName = this.state.group;
this.state.children.forEach(function(tab) {
childrenToRender.push(
React.createElement(
'input', {
type: 'radio',
name: groupName,
id: tab.internalId,
checked: true,
'aria-hidden': 'true'
}
)
);
childrenToRender.push(
React.createElement(
'label', {
'htmlFor': tab.internalId,
'aria-hidden': 'true'
},
'demo-tab'
)
);
childrenToRender.push(React.createElement('div', {}, tab.props.children));
});
return React.createElement('div', {
'className': 'tabs'
}, childrenToRender);
}
}
// This works fine
ReactDOM.render(
React.createElement(Tabs, {}, [React.createElement(Tab, {}, 'Hello world')]),
document.getElementById('root')
);
// This fails with the error mentioned above
// ReactDOM.render(
// <Tabs>
// <Tab>Hello, world!</Tab>
// </Tabs>,
// document.getElementById('root')
// );
<link rel="stylesheet" href="https://gitcdn.link/repo/Chalarangelo/mini.css/master/dist/mini-default.min.css">
<script src="https://unpkg.com/react#latest/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#latest/dist/react-dom.js"></script>
<script src="https://unpkg.com/babel-standalone#6.15.0/babel.min.js"></script>
<div id="root"></div>
Update: This only happens if I actually pass only one <Tab> to the <Tabs> due to the way it's processed. If, for example, I use the following code, I can use JSX to render the component and its contents:
ReactDOM.render(
<Tabs>
<Tab>Hello, world!</Tab>
<Tab>Hello, world!</Tab>
</Tabs>,
document.getElementById('root')
);
After checking out what babel output from the JSX code, I realized that it was not ouputting something like [React.createElement(Tab, {}, 'Hello world')] but rather something more like React.createElement(Tab, {}, 'Hello world'), meaning it was not an array, thus causing problems with .forEach().
To anyone interested, what I did was check if this.props.children is an array and, if not, to actually turn it into one. Sample below:
if (!Array.isArray(this.props.children))
var tempProps = [this.props.children];
else
var tempProps = this.props.children;
tempProps.forEach(
// Rest of the code is pretty much the same as before
);
This is not a very elegant solution, so feel free to post more elegant answers if you know any.

Categories