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/
Related
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 );
} );
});
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.
I'm newbie to CKEDITOR. It might sound worthless to some of you to answer this questions. But, I'm struggling to find a solution for my problem for the past few hours.
Aim :
I would like to add an event listener to particular kind of element ( For ex : span )
What i tried :
I used contentDom event thrown by CKEDITOR, to add the event listener to span elements.
Problem :
However, Adding event listener to span will be applicable for the span which are currently available in editor. But, Not for the elements ( span ) which will be created by the user in future. What should i do now?
Use the benefits of event bubbling [1, 2]. Attach the listener to the topmost element of the editor (editable) and filter out the events:
CKEDITOR.replace( 'editor1', {
on: {
contentDom: function() {
this.editable().on( 'click', function( evt ) {
var target = evt.data.getTarget();
if ( target.is( 'span' ) ) {
console.log( 'clicked span!' );
}
} );
}
}
} );
I'm working on a CKEditor plugin for annotating text and adding margin comments, but I'd like some of my custom toolbar buttons to be enabled only when the user has already selected a range of text. Whenever the user is typing, or the cursor is at a single-point (instead of a range), the buttons (and their associated commands) should be disabled.
I'm a pretty experienced plugin author, and I've spent a fair amount of time hunting through the core API docs, but I haven't found anything yet that looks like it'll help.
Your case is a little tricky, because selection change event is not well implemented across browsers, FF is the main problem.
In your case you'll going to need check selection changes very frequently, as you're interested in all selection changes therefore CKEditor selectionChange won't fit it.
Fortunately there's a selectionCheck event in editor that fires much more frequently and is implemented for FF.
Solution:
Here you have init method of a plugin that I've mocked to solve your problem. It will disable / enable Source button the way you explained.
I've already added throttling to this function, so that customers with less expansive machine can admire your feature :)
init: function( editor ) {
// Funciton depending on editor selection (taken from the scope) will set the state of our command.
function RefreshState() {
var editable = editor.editable(),
// Command that we want to control.
command = editor.getCommand( 'source' ),
range,
commandState;
if ( !editable ) {
// It might be a case that editable is not yet ready.
return;
}
// We assume only one range.
range = editable.getDocument().getSelection().getRanges()[ 0 ];
// The state we're about to set for the command.
commandState = ( range && !range.collapsed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
command.setState( commandState );
}
// We'll use throttled function calls, because this event can be fired very, very frequently.
var throttledFunction = CKEDITOR.tools.eventsBuffer( 250, RefreshState );
// Now this is the event that detects all the selection changes.
editor.on( 'selectionCheck', throttledFunction.input );
// You'll most likely also want to execute this function as soon as editor is ready.
editor.on( 'instanceReady', function( evt ) {
// Also do state refresh on instanceReady.
RefreshState();
} );
}
If you are working on a plugin, I guess that you are registering commands with the ckeditor.
In that case, you should provide a refresh method, which will be called by the CKEditor to update the state of the button when needed:
Defined by the command definition, a function to determine the command
state. It will be invoked when the editor has its states or selection
changed.
You can see examples of implementation in several of the plugins developed by the CKEditor team. Here is one taken from the source code of the Link plugin:
refresh: function( editor, path ) {
var element = path.lastElement && path.lastElement.getAscendant( 'a', true );
if ( element && element.getName() == 'a' && element.getAttribute( 'href' ) && element.getChildCount() )
this.setState( CKEDITOR.TRISTATE_OFF );
else
this.setState( CKEDITOR.TRISTATE_DISABLED );
}
Here is the code required to answer exactly the question using the refresh method as suggested by Gyum Fox.
I mention that to make it work, contextSensitive has to be set to 1.
editor.addCommand( 'myCommand', {
exec: function( editor ) {
// Custom logic of the command
},
refresh: function( editor ) {
var editable = editor.editable();
range = editable.getDocument().getSelection().getRanges()[ 0 ]; // We assume only one range.
commandState = ( range && !range.collapsed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED;
this.setState( commandState );
},
contextSensitive: 1
});
EDIT: I noticed some refresh issues on my side. So, because of that, I'd go for the Marek Lewandowski answer.
I'd like to place the caret at the end of the text after focus().
There are a lot of solutions for CKEditor 3, I tried around three of them, but they don't seem to work for CKEditor 4.
Note: I'm using inline-editing with contenteditable="true".
This should do the job (it WFM on themed and inline editors):
CKEDITOR.inline( 'editable', {
on: {
focus: function( evt ) {
setTimeout( function() {
var editor = evt.editor,
range = editor.createRange();
range.moveToElementEditEnd( editor.editable() );
range.select();
range.scrollIntoView();
}, 100 );
}
}
} );
Note that the timeout is required because on focus selection is automatically placed at the beginning or in place which was clicked, so you need to wait a while to overwrite that behaviour. You can check shorter timeouts of course.