How to get data from CKEditor 5 instance - ckeditor

I know that for CKEditor 4, you can get the textarea data like this:
var content = CKEDITOR.instances['comment'].getData();
How is this done for CKEditor 5?

You can find the answer in the Basic API guide.
Basically, in CKEditor 5 there's no single global editors repository (like the old CKEDITOR.instances global variable). This means that you need to keep the reference to the editor that you created and use that reference once you'll want to retrieve the data:
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
editor.getData(); // -> '<p>Foo!</p>'
} )
.catch( error => {
console.error( error );
} );
If you need to retrieve the data on some other occasions (who would read it just after initializing the editor, right? ;)), then save the reference to the editor in some shared object of your application's state or some variable in the scope:
let theEditor;
ClassicEditor
.create( document.querySelector( '#editor' ) )
.then( editor => {
theEditor = editor; // Save for later use.
} )
.catch( error => {
console.error( error );
} );
function getDataFromTheEditor() {
return theEditor.getData();
}
See this JSFiddle: https://jsfiddle.net/2h2rq5u2/
EDIT: If you need to manage more than one editor instance, see CKEDITOR 5 get editor instances.

Declare a global variable and then use editor.getData(). Something like this:
var editor;
ClassicEditor
.create(document.querySelector('#editor'))
.then(editor => {
editor=editor;
})
.catch(error => {
console.error(error);
});
Then, in your event handler, this should work:
editor.getData();

I ran into what felt like a unique situation where I was loading multiple instances of CKeditor5 on a page but I also had features to dynamically add/remove/rearrange text areas that had editors applied to them.
Creating an array that contained all the instances was manageable but also a bit more complicated to keep track of when adding/removing/rearrange editor instances.
I instead decided to obtain the innerHTML of the editor instances by selecting all .ck-editor__editable classes and iterating over them to check the content.
document.querySelector("button[type='submit']").addEventListener('click', (event) => {
event.preventDefault();
const domEditableElements = document.querySelectorAll('.ck-editor__editable');
for (let i = 0; i < domEditableElements.length; ++i) {
let elementData = domEditableElements[i].innerHTML
if (elementData === '<p><br data-cke-filler="true"></p>') {
console.log(`Field ${i} is empty`);
}
}
});
Console logging indicated that ck-editor stored <p><br data-cke-filler="true"></p> in blank fields.

Related

Unable to Drag and drop into iframe using cypress

Hi I have to drag a component which is outside a frame and drop into a iframe body(dragable source)
but it is not draging my component into iframe dragable source
this is code which is am using for get into the iframe and it is working means it is going into the iframe and finding element
const getIframeDocument = () => {
return cy.get(".gjs-frame").its("0.contentDocument").should("exist");
};
const getIframeBody = () => {
// get the document
return (
getIframeDocument()
// automatically retries until body is loaded
.its("body")
.should("not.be.undefined")
// wraps "body" DOM element to allow
// chaining more Cypress commands, like ".find(...)"
.then(cy.wrap)
);
};
and
for drag and drop into iframe like this
getIframeBody().find("#wrapper").as("Target");
cy.get('[title="Url"]').drag("#Target");
this is not giving me any error this drag function working but at the same time drag and drop is not happening
drag and drop execution pic must see this
you can do this by using this code. You just need to change selectors according to your website.
// Define Locators.
drag_element = '.blockbuilder-content-tools .blockbuilder-content-tool:nth-
child(6)';
drop_element = '#u_body #u_row_2';
text_indentation = '[aria-label="Align center"]';
new_box_text = 'This is a new Text block. Change the text.'
// Get iframe body and then drag & drop element.
iframe_interaction(){
let ifm = cy.get('iframe');
ifm
.should(iframe => expect(iframe.contents().find('body')))
.then(iframe => cy.wrap(iframe.contents().find('body')))
.within({}, $iframe => {
cy.get(this.drag_element).drag(this.drop_element, { force: true })
cy.contains(this.new_box_text).dblclick({force: true})
cy.get(this.text_indentation).click()
})
Github Link: https://github.com/sardar-usman/Cypress--Drag-and-Drop

React-Admin | Cannot upload a file using FileInput

First time with React-Admin. I am using it to create a panel that basically monitors some parameters that I get from an API. However, one section requires a .csv file to be uploaded. I am trying to implement it with FileInput but I am unable to catch the file. I don't understand how to do it.
File selection step (from pc to browser) is working properly, but my problem is that I cannot handle the file after that step. I read the docs, but I don't know how to do it. I tried many different ways but I am getting crazy.
Below is the basic code. I guess I have to add a handler or something similar but, how? I have little experience with React too. I know the basics, but I just built a couple of (super) simple apps. Just for learn.
// UploadFile.js
...
export const UploadSection = props => (
<SimpleForm>
<FileInput source="csvFile" label="Upload file (.csv)" accept="text/csv" >
<FileField source="src" title="title" />
</FileInput>
</SimpleForm>
);
// App.js
...
const App = () => (
<Admin dataProvider={dataProvider} authProvider={authProvider} >
...
<Resource name="uploadSection" list={UploadSection} />
...
</Admin>
);
The question:
How can I catch the .csv file?
Thanks in advance!
After working on it during hours I got it working.
First problem (the one I commented #selens answer): I got Uncaught TypeError: _this.props.save is not a function because I wasn't working in Create or Edit View. It seems you need to use FileInput in Create or Edit Views. If not, Save button won't work.
From docs:
save: The function invoked when the form is submitted. This is passed automatically by react-admin when the form component is used inside Create and Edit components.
Second problem: In my case I upload one file at a time (multiple={false} in FileInput). However, the code addUploadFeature.js is ready to use with multiple files. So, I edited part of addUploadFeature.js to upload just one file. See the complete file below.
// addUploadFeature.js
const convertFileToBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file.rawFile);
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
});
const addUploadFeature = requestHandler => (type, resource, params) => {
if (type === 'UPDATE' && resource === 'myResource') {
if (params.data.myFile) {
// NEW CODE HERE (to upload just one file):
const myFile = params.data.myFile;
if ( !myFile.rawFile instanceof File ){
return Promise.reject('Error: Not a file...'); // Didn't test this...
}
return Promise.resolve( convertFileToBase64(myFile) )
.then( (picture64) => ({
src: picture64,
title: `${myFile.title}`
}))
.then( transformedMyFile => requestHandler(type, resource, {
...params,
data: {
...params.data,
myFile: transformedMyFile
}
}));
}
}
return requestHandler(type, resource, params);
};
export default addUploadFeature;
After all, I was able to send the file to the server in Base64 format.
Hope this to be useful for somebody in the future ;)
I have a similar problem, I cannot upload a file and issue will be in accept prop.
You can use the following code:
<FileInput source="csvFile" label="Upload file (.csv)" accept=".csv" >
Instead of:
<FileInput source="csvFile" label="Upload file (.csv)" accept="text/csv" >
Have you checked the DataProvider and UploadFeature sections in the documentation?
If you have your own DataProvider, you can create a new file addUploadFeature.js and customize it as in the sample under this link:
https://marmelab.com/react-admin/DataProviders.html#extending-a-data-provider-example-of-file-upload

CKEditor 5 – get editor instances

I am migrating from CKEditor 4.7 to 5.
In CKE4, I would do something like this:
CKEDITOR.replace('text_area');
and then in another JS function I could get the data by
CKEDITOR.instances.text_area.getData().
But it doesn't appear that CKE5 has a function ClassicEditor.instances or something analogous.
I know I can store the editor instance as a global JS variable, but the code I am working with creates the editors in a general function, so I can't just create a global variable since I don't know the name of the editor a priori. There can also be several editors active on the screen at the same time.
Is there no analog in CKE5 to the old instances that would allow me to get an editor instance from just the id of the textarea it replaced?
I guess I could create my own global array to hold the editor instances, but I would rather not if there is something built in and better-supported
This question was already answered in How to get data from CKEDITOR5 instance, but let's consider here a case with more than one editor instance.
I guess I could create my own global array to hold the editor instances, but I would rather not if there is something built in and better-supported
There's no repository of editor instances. We could add it but we don't feel that this is an essential feature. It's actually something that people got used to in CKEditor 4 so they never thought and learned how to manage their editors by themselves.
Also, the reason why there's no single repository of instances is that there's no central singleton object at all. You may implement multiple editor classes and they don't have to know about each other. To come up with a repository, we'd need to centralise these things again.
So, as you pointed out yourself, a simple way to store all instances would be by having a global (global within your app or module, not necessarily a "global JS variable") map of these instances.
The keys to these instances can be the ids of elements on which you initialised editors:
const editors = {}; // You can also use new Map() if you use ES6.
function createEditor( elementId ) {
return ClassicEditor
.create( document.getElementById( elementId ) )
.then( editor => {
editors[ elementId ] = editor;
} )
.catch( err => console.error( err.stack ) );
}
// In real life, you may also need to take care of the returned promises.
createEditor( 'editor1' );
createEditor( 'editor2' );
// Later on:
editors.editor1.getData();
What if you don't assign ids to elements in the DOM? If you use ES6, then it's not a problem. Elements, like other objects, can be keys of a map.
const editors = new Map();
function createEditor( elementToReplace ) {
return ClassicEditor
.create( elementToReplace )
.then( editor => {
editors.set( elementToReplace, editor );
} )
.catch( err => console.error( err.stack ) );
}
// In real life, you may also need to take care of the returned promises.
createEditor( textarea1 );
createEditor( textarea2 );
// Later on:
editors.get( textarea1 ).getData();
If you can't use ES6, then you'd need to do a bit more – e.g. dynamically assign some data-editor-id attributes to elements on which you create the editors.
It's not the first time I'm trying to remind myself how to access the CKEditor instance on a production website having just access to the DOM via the developer console, so a reminder for myself ;)
https://ckeditor.com/docs/ckeditor5/latest/builds/guides/faq.html#how-to-get-the-editor-instance-object-from-the-dom-element
It's possible to access the editor instance using the ckeditorInstance property which is available on the contenteditable element that CKEditor 5 is using. You can access this DOM element via e.g. the .ck-editor__editable class.
// A reference to the editor editable element in the DOM.
const domEditableElement = document.querySelector( '.ck-editor__editable' );
// Get the editor instance from the editable element.
const editorInstance = domEditableElement.ckeditorInstance;
// Now you can use the editor instance API.
editorInstance.setData( '<p>Hello world!<p>' );
Running multiple copies of the editor using jQuery and a class selector:
$( '.editor' ).each( function() {
InlineEditor
.create( this )
.catch( error => {
console.error( error );
} );
});

Drag and drop external object into CKEditor

I see in CKEitor 4.5 there is a new drag and drop system. I would like to drop external DIVs or SPANs into my CkEditor and have them turn into "placeholders" "fake objects" or "protected source" objects. I.e., the dropped object should turn into arbitrary HTML that's related to the content.
The available demos seem to be about uploading content, but this is different and I'd appreciate a demo ...
Yes, it is possible. CKEditor 4.5 is in the beta phase at the moment, what means there is no tutorials yet, but here is sample how to do it.
First, you need to mark your data on dragstart. You can simple set text:
dragstart( evt ) {
evt.dataTransfer.setData( 'text', 'foo' );
} );
But then you need to make your text unique, otherwise every time someone drop foo it will be recognize as your container.
I prefer to use CKEditor data transfer facade, which let you use custom data type on every browser (including IE 8+):
dragstart( evt ) {
var evt = { data: { $: $evt } }; // Create CKEditor event.
CKEDITOR.plugins.clipboard.initDragDataTransfer( evt );
evt.data.dataTransfer.setData( 'mydatatype', true );
// Some text need to be set, otherwise drop event will not be fired.
evt.data.dataTransfer.setData( 'text', 'x' );
} );
Then in the CKEDITOR you can recognize this data and set your html to be dropped. You can replace dropped content whit whatever you need. Simple set text/html data in the drop event:
editor.on( 'drop', function( evt ) {
var dataTransfer = evt.data.dataTransfer;
if ( dataTransfer.getData( 'mydatatype' ) ) {
dataTransfer.setData( 'text/html', '<div>Bar</div>' );
}
} );
You can find working sample here: http://jsfiddle.net/oqzy8dog/3/

Tinymce is (sometimes) undefined

I'm using Tinymce (with jQuery) in a project I'm working at; we use a rich text editor for users to input information; however, sometimes when loading the page Firefox and Chrome will detect a 'tinymce is not defined' error (sometimes at different lines of the code), while other times the page will load just fine. What's weird is that it works perfectly with IE.
Here's a bit of the code I'm using:
view.find('textarea.rich-text').each(function () {
$(this).tinymce( /* ...rules... */);
});
And later on
_variable.find("#summary").tinymce().setContent(content);
This line is where the error (sometimes) gets caught. It seems to me that the problem is a loading issue, even though the tinyMCE plugin is initialized about 5000 lines prior this line.
Update: For now I have managed to 'solve' the problem with a setTimeout, but this seems like a really ugly way to do it.
A few points:
You don't mention whether or not the TinyMCE initialization is done within a jQuery ready event function. It should be of course.
You don't need the each loop. You can just say:
$('textarea.rich-text').tinymce({
script_url : '../js/tinymce/jscripts/tiny_mce/tiny_mce.js',
theme : "advanced",
...
});
You don't need the call to find since you are just selecting by id. Just do:
$("#summary").tinymce().setContent(content);
Your real issue is probably that tinymce has not finished initializing itself when you get the error. You see it has to load a script from the configured script_url. That may take a while. Therefore, you have to make use of a callback such as oninit.
If you do not have control over init method of TinyMCE then, you can follow this solution.
jQuery(document).ready(function($) {
function myCustomSetContent( id, content ) {
// Check if TinyMCE is defined or not.
if( typeof tinymce != "undefined" ) {
var editor = tinymce.get( id );
// Check if TinyMCE is initialized properly or not.
if( editor && editor instanceof tinymce.Editor ) {
editor.setContent( text );
editor.save( { no_events: true } );
} else {
// Fallback
// If TinyMCE is not initialized then directly set the value in textarea.
//TinyMCE will take up this value when it gets initialized.
jQuery( '#'+id ).val( text );
}
return true;
}
return false;
}
function myCustomGetContent( id ) {
// Check if TinyMCE is defined or not.
if( typeof tinymce != "undefined" ) {
var editor = tinymce.get( id );
// Check if TinyMCE is initialized properly or not.
if( editor && editor instanceof tinymce.Editor ) {
return editor.getContent();
} else {
// Fallback
// If TinyMCE is not initialized then directly set the value in textarea.
// TinyMCE will take up this value when it gets initialized.
return jQuery( '#'+id ).val();
}
}
return '';
}
$(".class-to-update-content").on("click", function(e) {
myCustomSetContent( "tinymce-editor-id", "New Content in Editor" );
});
$(".class-to-get-content").on("click", function(e) {
$("div.class-to-display-content").html( myCustomGetContent( "tinymce-editor-id" ) );
});
});
Ref : http://blog.incognitech.in/tinymce-undefined-issue/
EDIT: Solution included

Resources