React File Upload Integration With CKEditor5 In SharePoint Framework (SPFx)

In this blog, we will learn to integrate React File Upload control to CKEditor5 (Classic Editor) using SharePoint Framework.

What is Classic Editor in CKEditor5?

 
Classic editor is what most users traditionally learn to associate with a rich text editor — a toolbar with an editing area placed in a specific position on the page, usually as a part of a form that you use to submit some content to the server.
 
Note
Ckeditor5 Classic Editor is not yet supported in IE11 browser.
 
The below code is tested in Chrome, Mozilla and Edge.
 
Code Usage
 
Install "ckeditor5-classic" from your node package manager console as shown below.
  1. PS C:\XXXX\SPFxSolutions\SpFxRichTextEditor>npm install --save ckeditor5-classic   

Install "react-file-reader" from your node package manager console as shown below.

  1. PS C:\XXXX\SPFxSolutions\SpFxRichTextEditor>npm install --save react-file-reader   

Install "office-ui-fabric-react" from your node package manager console as shown below.

  1. PS C:\XXXX\SPFxSolutions\SpFxRichTextEditor>npm install --save office-ui-fabric-react   
Import "Classic Editor", "React File Upload" to your .tsx file as shown below.
  1. import ClassicEditor from 'ckeditor5-classic';     
  2. import ReactFileReader from 'react-file-reader';    
  3. import {Icon} from 'office-ui-fabric-react/lib/Icon';  
Initiate default toolbar configuration settings for Classic Editor.
  1. ClassicEditor.defaultConfig = {        
  2.   toolbar: {        
  3.     items: [        
  4.       'heading',        
  5.       '|',        
  6.       'bold',        
  7.       'italic',        
  8.       'fontSize',        
  9.       'fontFamily',        
  10.       'fontColor',        
  11.       'fontBackgroundColor',        
  12.       'link',        
  13.       'bulletedList',        
  14.       'numberedList',        
  15.       'imageUpload',        
  16.       'insertTable',        
  17.       'blockQuote',        
  18.       'undo',        
  19.       'redo'        
  20.     ]        
  21.   },        
  22.   image: {        
  23.     toolbar: [        
  24.       'imageStyle:full',        
  25.       'imageStyle:side',        
  26.       '|',        
  27.       'imageTextAlternative'        
  28.     ]        
  29.   },        
  30.   fontFamily: {        
  31.     options: [        
  32.       'Arial',        
  33.       'Helvetica, sans-serif',        
  34.       'Courier New, Courier, monospace',        
  35.       'Georgia, serif',        
  36.       'Lucida Sans Unicode, Lucida Grande, sans-serif',        
  37.       'Tahoma, Geneva, sans-serif',        
  38.       'Times New Roman, Times, serif',        
  39.       'Trebuchet MS, Helvetica, sans-serif',        
  40.       'Verdana, Geneva, sans-serif'        
  41.     ]        
  42.   },        
  43.   language: 'en'        
  44. };        
Declare the state variable to store ckeditor event
  1. export interface ICKeditorState {    
  2.   CKEditorEvent:any;    
  3. }  
Initiatlize the state variable in your constructor
  1. constructor(props:  ISpFxRichTextEditorProps) {    
  2.     super(props);    
  3.       this.state = {    
  4.         CKEditorEvent:''           
  5.     };        
  6.   }    
  7. }  
Insert text area and React File Upload Control to render the CKeditor5 on load.
  1. public render(){        
  2.     return (        
  3.       <div id="fileUpload1">    
  4.           <ReactFileReader handleFiles={this.HandleFiles.bind(this)} base64={true} >    
  5.             <Icon iconName="BulkUpload" className="ms-IconExample" />    
  6.           </ReactFileReader>    
  7.         </div>    
  8.       <div>        
  9.         <textarea id="editor1"></textarea>         
  10.       </div>        
  11.     );        
  12.   }    
Replace text area with CKEditor5 on componentDidMount and store in the CKeditor event in state variable.
  1. componentDidMount(){        
  2.   this.InitializeCKeditor();        
  3. }  
  1. /* Load CKeditor RTE*/      
  2.   public InitializeCKeditor(): void {      
  3.     try {      
  4.       /*Replace textarea with classic editor*/      
  5.       ClassicEditor      
  6.         .create(document.querySelector("#editor1"), {    
  7.         }).then(editor => {      
  8.           console.log("CKEditor5 initiated");    
  9.           this.setState({CKEditorEvent : editor});            
  10.         }).catch(error => {      
  11.           console.log("Error in Classic Editor Create " + error);      
  12.         });      
  13.     } catch (error) {      
  14.       console.log("Error in  InitializeCKeditor " + error);      
  15.     }      
  16.   }    
HandleFiles - Upload to sharepoint library and retrieve the server relative url.
  1. /* For File Upload Control - Upload to sharepoint library and retrieve the server relative url */    
  2.    HandleFiles = files => {    
  3.      try {    
  4.       this.HandleUploadedFiles(files, "fileUpload1"this.state.CKEditorEvent,    
  5.       "TestDocLibrary");          
  6.      } catch (error) {    
  7.        console.log("Error in HandleFiles " + error);    
  8.      }    
  9.    }    
Handle Uploaded files from React file reader control
  1. /*  Handle Uploaded files from React file reader control */    
  2.     /*  files : Uploaded files parameter    
  3.         fileDivId : File div id.  
  4.         rteEvent : CKeditor5 event captured during initalzing ckeditor5  
  5.         DocLibPath : SharePoint Document Library Path to upload the files  
  6.     */      
  7.     public HandleUploadedFiles(files,fileDivId,rteEvent,DocLibPath):void{    
  8.             
  9.         try {    
  10.             var fileExtension = this.GetFileExtension(files.fileList[0].name);    
  11.             var date = new Date();    
  12.             var guid = date.valueOf();    
  13.             var fileInternalName = files.fileList[0].name.substr(0, files.fileList[0].name.lastIndexOf('.'));    
  14.             var fileName = fileInternalName + "-" + guid + "." + fileExtension;    
  15.             var myBlob = this.Base64ToArrayBuffer(files.base64);    
  16.             this.GetFolderServerRelativeURL(myBlob, fileName,DocLibPath).then((fileURL) => {    
  17.                 this.AppendToRichTextEditor(fileURL, files.fileList[0].name, fileExtension, fileDivId, rteEvent);    
  18.             }).catch((error) => {    
  19.                 alert("Error while processing the promise call " + error)    
  20.             });    
  21.         } catch (error) {    
  22.             console.log("Error in HandleFiles " + error);    
  23.         }    
  24.     }    
Get File Extension from the uploaded files
  1. /*  Get file extension from the uploaded files */    
  2.     /*  filename : string parameter  */    
  3.     public GetFileExtension(filename) {    
  4.         try{    
  5.             return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename)[0] : undefined;    
  6.         }catch(error){    
  7.             console.log("Error in Get File Extension  " + error);    
  8.         }            
  9.     }    
Convert base 64 image format to bytes array
  1. /*  Convert uploaded files from base64 to bytes array */    
  2.     public Base64ToArrayBuffer(base64) {    
  3.         try {    
  4.             var binary_string = window.atob(base64.split(',')[1]);    
  5.             var len = binary_string.length;    
  6.             var bytes = new Uint8Array(len);    
  7.             for (var i = 0; i < len; i++) {    
  8.                 bytes[i] = binary_string.charCodeAt(i);    
  9.             }    
  10.             return bytes.buffer;    
  11.         } catch (error) {    
  12.             console.log("Error in Base64ToArrayBuffer " + error);    
  13.         }    
  14.     }    
Upload the file to sharepoint document library and get the server relative url of the uploaded file.
 
Note
Make sure your "webpart.ts" should contain site url and sphttpclient as shown below.
  1. public render(): void {    
  2.     const element: React.ReactElement<ISpFxRichTextEditorProps > = React.createElement(    
  3.       SpFxRichTextEditor,    
  4.       {    
  5.         spHttpClient: this.context.spHttpClient,      
  6.         siteUrl: this.context.pageContext.web.absoluteUrl,    
  7.       }    
  8.     );    
  1. /*  Get uploaded folder server relative url */    
  2.     public GetFolderServerRelativeURL(file, FinalName, DocumentLibPath): Promise<any> {    
  3.         try {    
  4.             return new Promise((resolve) => {    
  5.                 setTimeout(() => {    
  6.                     const spOpts: ISPHttpClientOptions = {    
  7.                         body: file    
  8.                     };    
  9.                     var redirectionURL = this.props.siteUrl + "/_api/Web/GetFolderByServerRelativeUrl('"+DocumentLibPath+"')/Files/Add(url='" + FinalName + "', overwrite=true)?$expand=ListItemAllFields"    
  10.                     const response = this.props.spHttpClient.post(redirectionURL, SPHttpClient.configurations.v1, spOpts).then((response: SPHttpClientResponse) => {      
  11.                         response.json().then(async (responseJSON: any) => {      
  12.                           var serverRelURL = await responseJSON.ServerRelativeUrl;    
  13.                           resolve(serverRelURL);        
  14.                         });      
  15.                       });      
  16.                 }, Math.floor(Math.random() * 1000));    
  17.             });    
  18.         } catch (error) {    
  19.             console.log("Error in GetFolderServerRelativeURL " + error);    
  20.         }    
  21.     
  22.     }  
Append updated file content to CKEditor selection.
  1. /* Append updated file content to ckeditor */    
  2.     public AppendToRichTextEditor(fileURL, displayName, fileExtension,fileDivId,rteEvent) {    
  3.         try {    
  4.             if (fileExtension.indexOf('png') == 0) {    
  5.                console.log('uploaded png file');     
  6.             } else {    
  7.                 var appendText = '';    
  8.                 var fileExt = fileExtension.toLowerCase();    
  9.                 appendText = "<div id='"+fileDivId+"'><img src='/_layouts/15/images/ic" + fileExt + ".png'><a href='" + fileURL + "' >" + displayName + "</a></div>";    
  10.                 const viewFragment = rteEvent.data.processor.toView(appendText);    
  11.                 const modelFragment = rteEvent.data.toModel(viewFragment);    
  12.                 rteEvent.model.insertContent(modelFragment, rteEvent.model.document.selection);                    
  13.             }    
  14.         } catch (error) {    
  15.             console.log("Error in AppendToRichTextEditor " + error);    
  16.         }    
  17.     }    
Please feel free to share your comments.
 
I hope this helps!!!!!