CKEditor 5 – get editor instances - ckeditor

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 );
} );
});

Related

How to get data from CKEditor 5 instance

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.

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/

Enable CKEditor toolbar button only with valid text selection?

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.

Using autocomplete on a form not in DOM

Let me begin by saying that I'm on JQuery version 1.3.2. Upgrading at this point is not an option.
I have a form that is added in by a templating system after the page load occurs. I'm very new to JQuery but it's my understanding that live will allow me to access it.
The function for the autocomplete already serves a user search and works well. I want to share this function for the admin part of the site as well as the query is almost identically the same.
The clientName element is from the dynamically added form. If I use the code below, nothing happens; no data is retrieved.
$('#clientName')
.site_clientAutocomplete(
'admin',
function( $event, $result, $data )
{
$('#clientName').val($data.ClientName);
}
);
If I wrap it inside the following code, it will work, sort of. I have to click inside the input box several times before I can get anything back from the database.
$("#clientName").live('keydown', function(){
});
Can someone tell me how I can get this autocomplete to function properly?
live is just for handling events that occur on elements matching the selector now or in the future. If you need more robust detection of elements matching your selector, you can either apply the widget when the content is added, or you can use the livequery plugin
With livequery, you can "listen" for new elements matching your selector and run a function when that event occurs. In your case, this would go something like this:
$('clientName').livequery(function () {
$('#clientName').site_clientAutocomplete(
'admin',
function( $event, $result, $data )
{
$('#clientName').val($data.ClientName);
}
);
});

Titanium Mobile: reference UI elements with an ID?

How do you keep track of your UI elements in Titanium? Say you have a window with a TableView that has some Switches (on/off) in it and you'd like to reference the changed switch onchange with a generic event listener. There's the property event.source, but you still don't really know what field of a form was just toggled, you just have a reference to the element. Is there a way to give the element an ID, as you would with a radiobutton in JavaScript?
Up to now, registered each form UI element in a dictionary, and saved all the values at once, looping through the dictionary and getting each object value. But now I'd like to do this onchange, and I can't find any other way to do it than create a specific callback function for each element (which I'd really rather not).
just assign and id to the element... all of these other solution CAN work, but they seem to be over kill for what you are asking for.
// create switch with id
var switcher0 = Ti.Ui.createSwitch({id:"switch1"});
then inside your event listener
myform.addEventListener('click', function(e){
var obj = e.source;
if ( obj.id == "switch1" ) {
// do some magic!!
}
});
A simple solution is to use a framework that helps you keep track of all your elements, which speeds up development quite a bit, as the project and app grows. I've built a framework of my own called Adamantium.js, which lets you use a syntax like jQuery to deal with your elements, based on ID and type selectors. In a coming release, it will also support for something like classes, that can be arbitrarily added or removed from an element, tracking of master/slave relationships and basic filter methods, to help you narrow your query. Most methods are chainable, so building apps with rich interaction is quick and simple.
A quick demo:
// Type selector, selects all switches
$(':Switch')
// Bind a callback to the change event on all switches
// This callback is also inherited by all new switch elements
$(':Switch').bind('change', function (e) {
alert(e.type + ' fired on ' + e.source.id + ', value = ' + e.value);
});
// Select by ID and trigger an event
$('#MyCustomSwitch').trigger('change', {
foo: 'bar'
});
Then there's a lot of other cool methods in the framework, that are all designed to speed up development and modeled after the familiar ways of jQuery, more about that in the original blog post.
I completely understand not wanting to write a listener to each one because that is very time consuming. I had the same problem that you did and solved it like so.
var switches = [];
function createSwitch(i) {
switches[i] = Ti.UI.createSwitch();
switches[i].addEventListener('change', function(e) {
Ti.API.info('switch '+i+' = '+e.value);
});
return switches[i];
}
for(i=0;i<rows.length;i++) {
row = Ti.UI.createTableViewRow();
row.add(createSwitch(i));
}
However keep in mind that this solution may not fit your needs as it did mine. For me it was good because each time I created a switch it added a listener to it dynamically then I could simply get the e.source.parent of the switch to interact with whatever I needed.
module Id just for the hold it's ID. When we have use id the call any another space just use . and use easily.
Try This
var but1 = Ti.Ui.createButton({title : 'Button', id:"1"});
window.addEventListener('click', function(e){
var obj = e.source;
if ( obj.id == "1" ) {
// do some magic!!
}
});
window.add(but1);
I, think this is supported for you.
how do you create your tableview and your switcher? usually i would define a eventListener function while creating the switcher.
// first switch
var switcher0 = Ti.Ui.createSwitch();
switch0.addEventListener('change',function(e){});
myTableViewRow.add(switch0);
myTableView.add(myTableViewRow);
// second switch
var switch1 = ..
so no generic event listener is needed.

Resources