DevExtreme v24.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

React Editors - Custom Text Editor Buttons

Text editors have built-in action buttons that allow users to open a drop-down menu, increase, decrease, or nullify the value, and perform other actions. To add custom action buttons for different scenarios, use the buttons[] array.

The USD/EUR rate is taken from www.wikipedia.org and is relevant on 14 April 2021
Backend API
import React, { useCallback, useMemo, useState } from 'react'; import { TextBox, Button as TextBoxButton, TextBoxTypes } from 'devextreme-react/text-box'; import { NumberBox, Button as NumberBoxButton, NumberBoxTypes } from 'devextreme-react/number-box'; import { DateBox, Button as DateBoxButton, DateBoxTypes } from 'devextreme-react/date-box'; import { ButtonTypes } from 'devextreme-react/button'; const millisecondsInDay = 24 * 60 * 60 * 1000; const currencyLabel = { 'aria-label': 'Multi Currency' }; const dateBoxLabel = { 'aria-label': 'Date' }; const passwordLabel = { 'aria-label': 'Password' }; function App() { const [passwordMode, setPasswordMode] = useState<TextBoxTypes.TextBoxType>('password'); const [currencyFormat, setCurrencyFormat] = useState('$ #.##'); const [currencyValue, setCurrencyValue] = useState(14500.55); const [dateValue, setDateValue] = useState(new Date().getTime()); const passwordButton = useMemo<ButtonTypes.Properties>( () => ({ icon: 'eyeopen', stylingMode: 'text', onClick: () => { setPasswordMode((prevPasswordMode: string) => (prevPasswordMode === 'text' ? 'password' : 'text')); }, }), [setPasswordMode], ); const currencyButton = useMemo<ButtonTypes.Properties>( () => ({ text: '€', stylingMode: 'text', width: 32, elementAttr: { class: 'currency', }, onClick: (e) => { if (e.component.option('text') === '$') { e.component.option('text', '€'); setCurrencyFormat('$ #.##'); setCurrencyValue((prevCurrencyValue: number) => prevCurrencyValue / 0.836); } else { e.component.option('text', '$'); setCurrencyFormat('€ #.##'); setCurrencyValue((prevCurrencyValue: number) => prevCurrencyValue * 0.836); } }, }), [setCurrencyFormat, setCurrencyValue], ); const todayButton = useMemo<ButtonTypes.Properties>( () => ({ text: 'Today', stylingMode: 'text', onClick: () => { setDateValue(new Date().getTime()); }, }), [setDateValue], ); const prevDateButton = useMemo<ButtonTypes.Properties>( () => ({ icon: 'spinprev', stylingMode: 'text', onClick: () => { setDateValue((prevDateValue: number) => prevDateValue - millisecondsInDay); }, }), [setDateValue], ); const nextDateButton = useMemo<ButtonTypes.Properties>( () => ({ icon: 'spinnext', stylingMode: 'text', onClick: () => { setDateValue((prevDateValue: number) => prevDateValue + millisecondsInDay); }, }), [setDateValue], ); const onDateChanged = useCallback( (e: DateBoxTypes.ValueChangedEvent) => { setDateValue(e.value); }, [setDateValue], ); const changeCurrency = useCallback( (data: NumberBoxTypes.ValueChangedEvent) => { setCurrencyValue(data.value); }, [setCurrencyValue], ); return ( <React.Fragment> <div className="dx-fieldset"> <div className="dx-field"> <div className="dx-field-label">Password TextBox</div> <div className="dx-field-value"> <TextBox placeholder="password" stylingMode="filled" defaultValue="password" inputAttr={passwordLabel} mode={passwordMode} > <TextBoxButton name="password" location="after" options={passwordButton} /> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Multi-currency NumberBox</div> <div className="dx-field-value"> <NumberBox showClearButton={true} showSpinButtons={true} format={currencyFormat} value={currencyValue} inputAttr={currencyLabel} onValueChanged={changeCurrency} > <NumberBoxButton name="currency" location="after" options={currencyButton} /> <NumberBoxButton name="clear" /> <NumberBoxButton name="spins" /> </NumberBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Advanced DateBox</div> <div className="dx-field-value"> <DateBox value={dateValue} stylingMode="outlined" inputAttr={dateBoxLabel} onValueChanged={onDateChanged} > <DateBoxButton name="today" location="before" options={todayButton} /> <DateBoxButton name="prevDate" location="before" options={prevDateButton} /> <DateBoxButton name="nextDate" location="after" options={nextDateButton} /> <DateBoxButton name="dropDown" /> </DateBox> </div> </div> </div> </React.Fragment> ); } export default App;
import React, { useCallback, useMemo, useState } from 'react'; import { TextBox, Button as TextBoxButton } from 'devextreme-react/text-box'; import { NumberBox, Button as NumberBoxButton } from 'devextreme-react/number-box'; import { DateBox, Button as DateBoxButton } from 'devextreme-react/date-box'; const millisecondsInDay = 24 * 60 * 60 * 1000; const currencyLabel = { 'aria-label': 'Multi Currency' }; const dateBoxLabel = { 'aria-label': 'Date' }; const passwordLabel = { 'aria-label': 'Password' }; function App() { const [passwordMode, setPasswordMode] = useState('password'); const [currencyFormat, setCurrencyFormat] = useState('$ #.##'); const [currencyValue, setCurrencyValue] = useState(14500.55); const [dateValue, setDateValue] = useState(new Date().getTime()); const passwordButton = useMemo( () => ({ icon: 'eyeopen', stylingMode: 'text', onClick: () => { setPasswordMode((prevPasswordMode) => (prevPasswordMode === 'text' ? 'password' : 'text')); }, }), [setPasswordMode], ); const currencyButton = useMemo( () => ({ text: '€', stylingMode: 'text', width: 32, elementAttr: { class: 'currency', }, onClick: (e) => { if (e.component.option('text') === '$') { e.component.option('text', '€'); setCurrencyFormat('$ #.##'); setCurrencyValue((prevCurrencyValue) => prevCurrencyValue / 0.836); } else { e.component.option('text', '$'); setCurrencyFormat('€ #.##'); setCurrencyValue((prevCurrencyValue) => prevCurrencyValue * 0.836); } }, }), [setCurrencyFormat, setCurrencyValue], ); const todayButton = useMemo( () => ({ text: 'Today', stylingMode: 'text', onClick: () => { setDateValue(new Date().getTime()); }, }), [setDateValue], ); const prevDateButton = useMemo( () => ({ icon: 'spinprev', stylingMode: 'text', onClick: () => { setDateValue((prevDateValue) => prevDateValue - millisecondsInDay); }, }), [setDateValue], ); const nextDateButton = useMemo( () => ({ icon: 'spinnext', stylingMode: 'text', onClick: () => { setDateValue((prevDateValue) => prevDateValue + millisecondsInDay); }, }), [setDateValue], ); const onDateChanged = useCallback( (e) => { setDateValue(e.value); }, [setDateValue], ); const changeCurrency = useCallback( (data) => { setCurrencyValue(data.value); }, [setCurrencyValue], ); return ( <React.Fragment> <div className="dx-fieldset"> <div className="dx-field"> <div className="dx-field-label">Password TextBox</div> <div className="dx-field-value"> <TextBox placeholder="password" stylingMode="filled" defaultValue="password" inputAttr={passwordLabel} mode={passwordMode} > <TextBoxButton name="password" location="after" options={passwordButton} /> </TextBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Multi-currency NumberBox</div> <div className="dx-field-value"> <NumberBox showClearButton={true} showSpinButtons={true} format={currencyFormat} value={currencyValue} inputAttr={currencyLabel} onValueChanged={changeCurrency} > <NumberBoxButton name="currency" location="after" options={currencyButton} /> <NumberBoxButton name="clear" /> <NumberBoxButton name="spins" /> </NumberBox> </div> </div> <div className="dx-field"> <div className="dx-field-label">Advanced DateBox</div> <div className="dx-field-value"> <DateBox value={dateValue} stylingMode="outlined" inputAttr={dateBoxLabel} onValueChanged={onDateChanged} > <DateBoxButton name="today" location="before" options={todayButton} /> <DateBoxButton name="prevDate" location="before" options={prevDateButton} /> <DateBoxButton name="nextDate" location="after" options={nextDateButton} /> <DateBoxButton name="dropDown" /> </DateBox> </div> </div> </div> </React.Fragment> ); } export default App;
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.tsx'; ReactDOM.render(<App />, document.getElementById('app'));
window.exports = window.exports || {}; window.config = { transpiler: 'ts', typescriptOptions: { module: 'system', emitDecoratorMetadata: true, experimentalDecorators: true, jsx: 'react', }, meta: { 'react': { 'esModule': true, }, 'typescript': { 'exports': 'ts', }, 'devextreme/time_zone_utils.js': { 'esModule': true, }, 'devextreme/localization.js': { 'esModule': true, }, 'devextreme/viz/palette.js': { 'esModule': true, }, }, paths: { 'npm:': 'https://unpkg.com/', }, defaultExtension: 'js', map: { 'ts': 'npm:plugin-typescript@4.2.4/lib/plugin.js', 'typescript': 'npm:typescript@4.2.4/lib/typescript.js', 'react': 'npm:react@17.0.2/umd/react.development.js', 'react-dom': 'npm:react-dom@17.0.2/umd/react-dom.development.js', 'prop-types': 'npm:prop-types@15.8.1/prop-types.js', 'rrule': 'npm:rrule@2.6.4/dist/es5/rrule.js', 'luxon': 'npm:luxon@1.28.1/build/global/luxon.min.js', 'es6-object-assign': 'npm:es6-object-assign@1.1.0', 'devextreme': 'npm:devextreme@24.1.7/cjs', 'devextreme-react': 'npm:devextreme-react@24.1.7/cjs', 'jszip': 'npm:jszip@3.10.1/dist/jszip.min.js', 'devextreme-quill': 'npm:devextreme-quill@1.7.1/dist/dx-quill.min.js', 'devexpress-diagram': 'npm:devexpress-diagram@2.2.13/dist/dx-diagram.js', 'devexpress-gantt': 'npm:devexpress-gantt@4.1.56/dist/dx-gantt.js', '@devextreme/runtime': 'npm:@devextreme/runtime@3.0.13', 'inferno': 'npm:inferno@7.4.11/dist/inferno.min.js', 'inferno-compat': 'npm:inferno-compat/dist/inferno-compat.min.js', 'inferno-create-element': 'npm:inferno-create-element@7.4.11/dist/inferno-create-element.min.js', 'inferno-dom': 'npm:inferno-dom/dist/inferno-dom.min.js', 'inferno-hydrate': 'npm:inferno-hydrate@7.4.11/dist/inferno-hydrate.min.js', 'inferno-clone-vnode': 'npm:inferno-clone-vnode/dist/inferno-clone-vnode.min.js', 'inferno-create-class': 'npm:inferno-create-class/dist/inferno-create-class.min.js', 'inferno-extras': 'npm:inferno-extras/dist/inferno-extras.min.js', 'devextreme-cldr-data': 'npm:devextreme-cldr-data@1.0.3', // SystemJS plugins 'plugin-babel': 'npm:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'npm:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', // Prettier 'prettier/standalone': 'npm:prettier@2.8.8/standalone.js', 'prettier/parser-html': 'npm:prettier@2.8.8/parser-html.js', }, packages: { 'devextreme': { defaultExtension: 'js', }, 'devextreme-react': { main: 'index.js', }, 'devextreme/events/utils': { main: 'index', }, 'devextreme/localization/messages': { format: 'json', defaultExtension: 'json', }, 'devextreme/events': { main: 'index', }, 'es6-object-assign': { main: './index.js', defaultExtension: 'js', }, }, packageConfigPaths: [ 'npm:@devextreme/*/package.json', 'npm:@devextreme/runtime@3.0.13/inferno/package.json', ], babelOptions: { sourceMaps: false, stage0: true, react: true, }, }; System.config(window.config);
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render(<App />, document.getElementById('app'));
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/24.1.7/css/dx.light.css" /> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="https://unpkg.com/core-js@2.6.12/client/shim.min.js"></script> <script src="https://unpkg.com/systemjs@0.21.3/dist/system.js"></script> <script type="text/javascript" src="config.js"></script> <script type="text/javascript"> System.import("./index.tsx"); </script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="app"></div> </div> </body> </html>
.dx-fieldset { min-height: 560px; width: 560px; margin: 0 auto; } .currency { min-width: 32px; } .dx-button.currency .dx-button-content { font-size: 120%; padding-left: 5px; padding-right: 5px; }

Each object in the buttons[] array should have the name field—the button's identifier. In addition, specify the button's location relative to the input text field and options of the Button component used as the action button.

The buttons[] array also accepts string values—the names of built-in buttons. Declare them in the order the buttons should have in the component. String and object declarations can be used in the same array.