How to trigger a popup model (A React Component) from CKEditor plugin button inside toolbar?
Requirement: Create a plugin that should have a button in the toolbar, On click should open a popup window which is a react component defined outside the plugin component.
export default class OpenPopup extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add('openPopup', locale => {
const view = new ButtonView(locale);
view.set({
label: 'Open Popup model',
icon: popupImage,
tooltip: true
});
// Callback executed once the button is clicked.
view.on('execute', () => {
});
return view;
});
}
}
Popup code
import React, { useEffect, useState } from 'react';
import PropTypes from "prop-types";
import { Modal } from 'react-bootstrap';
const Popup= ({ show = false }) => {
return (
<Modal
size="xl"
show={show}
dialogClassName="add-media-modal"
onHide={()=> {show = false}}
>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Add Media
</Modal.Title>
<button type="button" className="btn btn-primary btn-sm" onClick={handleCloseModal}>X</button>
</Modal.Header>
<Modal.Body>
</Modal.Body>
<Modal.Footer>
<button
type="button" className="btn btn-primary btn-sm"
>
Add
</button>
</Modal.Footer>
</Modal>
)
}
Popup.propTypes = {
show: PropTypes.bool
};
export default Popup
I was looking for various options or solutions to the above requirement.
Hello @pomek, Hope you are doing great! Can you please show some light here? It was a roadblock to me.
Thanks in advance!
@SuryaKavutarapu I'm trying to achieve the same thing, did you find a satisfying solution ?
I had the same problem, I solved it by adding a callback function to the config, the custom plugin just returns the state when you click the button
I'm not sure if this is the right way
// editor.tsx
import React, { FC, useState } from 'react';
import { CKEditor as Editor } from '@ckeditor/ckeditor5-react';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import editorConfiguration from './config';
......
import { Dialog } from '**dialog component**';
interface CKEditorProps {
...
}
const CKEditor: FC<CKEditorProps> = (props) => {
// dialog visible state
const [visible, setVisible] = useState(false);
......
return (
<div className='editor-content'>
<Editor
editor={DecoupledEditor}
config={{
...editorConfiguration,
importArticle: {
handleVisibleDialog: bool => setVisible(bool),
},
}}
......
/>
{/* my custom dialog */}
<Dialog header='Basic Modal' visible={visible} confirmOnEnter onClose={() => setVisible(false)}>
<p>This is a dialog</p>
</Dialog>
</div>
);
};
export default CKEditor;
// config.ts
......
import ImportArticle from './utils/import-article';
const editorConfiguration = {
plugins: [
......
ImportArticle,
],
toolbar: [
......
'importArticle',
],
......
};
export default editorConfiguration;
// import-article.ts
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
class ImportArticle extends Plugin {
init() {
const { editor } = this;
editor.ui.componentFactory.add('importArticle', () => {
const button = new ButtonView();
button.set({
label: 'label name',
withText: true,
});
button.on('execute', () => {
// get the custom callback function in config
const { handleVisibleDialog } = editor.config.get('importArticle');
handleVisibleDialog(true);
});
return button;
});
}
}
export default ImportArticle;
It seems a better way to use Portals
// editor.tsx
import React, { FC, useState } from 'react';
import { CKEditor as Editor } from '@ckeditor/ckeditor5-react';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import editorConfiguration from './config';
......
import { DialogWithBtn } from '**dialog component**';
interface CKEditorProps {
...
}
const CKEditor: FC<CKEditorProps> = (props) => {
const [importArticleWrap, setImportArticleWrap] = useState<Element | null>(null);
const handleReady = async (editor: Editor) => {
const importArticleWrapDom = document.querySelector('.toolbar-portals__import-article');
setImportArticleWrap(importArticleWrapDom);
};
return (
<div className='editor-content'>
<Editor
editor={DecoupledEditor}
config={editorConfiguration}
onReady={handleReady}
......
/>
<ImportArticleDialog portalContainer={importArticleWrap} />
</div>
);
};
export default CKEditor;
// import-article-dialog.tsx
import React, { FC } from 'react';
import { createPortal } from 'react-dom';
import { Dialog } from '...';
interface ImportArticleDialogProps {
portalContainer: Element | null;
}
const ImportArticleDialog: FC<ImportArticleDialogProps> = ({ portalContainer }) => {
......
return (
<>
{portalContainer
&& createPortal(
<button
className='ck ck-button ck-off ck-button_with-text'
onClick={() => do someing to show the dialog }
>
import article
</button>
portalContainer,
)}
<Dialog>
...
</Dialog>
</>
);
};
export default ImportArticleDialog;
// ImportArticle plugin
import { DecoupledEditor } from '@ckeditor/ckeditor5-editor-decoupled';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import View from '@ckeditor/ckeditor5-ui/src/view';
class ImportArticle extends Plugin {
init() {
const { editor } = this;
(editor as DecoupledEditor).ui.componentFactory.add('importArticle', () => {
const view = new View();
// Provide a container to insert custom react components by useing Portals
view.setTemplate({
tag: 'div',
attributes: {
class: 'toolbar-portals__import-article',
},
});
return view;
});
}
}
export default ImportArticle;
// config.ts
......
import ImportArticle from './utils/import-article';
const editorConfiguration = {
plugins: [
......
ImportArticle,
],
toolbar: [
......
'importArticle',
],
......
};
export default editorConfiguration;