CKEditor 4 keeps class on toggle - ckeditor

I'm using a custom plugin for CKEDITOR4 that does the following:
var pluginName = 'flowctrl';
var style = new CKEDITOR.style({ element: 'pre', attributes: {'class: flow'}});
editor.addCommand(pluginName , new CKEDITOR.styleCommand(style));
editor.attachStyleStateChange(style, function (state){
!editor.readOnly && editor.getCommand(pluginName).setState(state);
});
editor.ui.addButton('Flow_Control' {
label: 'Flow Control',
command: pluginName,
toolbar: 'insert'
});
In short, it changes <p> block that the cursor is on to <pre> and adds the class "flow"
Much like selecting "bold" for text, the button is pressed in while you're inside this <pre> block.
When you toggle the button off, it changes the <pre> back into <p>, however it retains the class "flow".
I'm fairly new to CKEDITOR, so I'm not sure what I'm missing in this context.
Any help is greatly appreciated.

Related

How to disable widget content nesting in CKEditor?

I have an editable custom widget that can be placed in CKEditor 4 by
clicking a button in the toolbar - works fine = does not allow nesting
drag & drop from outside of the editor - allows nesting
I do not want to let user to have nested content of the widget. On the other hand I do want users to able to edit the content of the widget.
NOTE
click the button to insert widget's content. Click inserted text and the button becomes not available for clicking. Click some other text and you would be able to insert again. This is desired behavior.
the widget button will not be present in the final version of the CKEditor application / widget. Only drag&drop way of inserting text will be available
Insert two widgets in the editor and try to drag&drop one inside the other one. It will not work. This is desired behavior.
Now try to insert a widget and then insert another one by drag&drop of the text "master or editable" into existing widget. It will be possible.
Could someone help me to set CKEditor a way so nesting is NOT possible?
Working jsfiddle.
init: function( editor ) {
editor.widgets.add( 'simplebox', {
button: 'Create a simple box',
template:
'<div class="simplebox">' +
'<h2 class="simplebox-title">Title</h2>' +
'<div class="simplebox-content"><p>Content<br>.<br>.<br>.</p></div>' +
'</div>',
editables: {
title: {
selector: '.simplebox-title',
allowedContent: 'br strong'
},
content: {
selector: '.simplebox-content',
allowedContent: 'p br ul ol li strong em'
}
},
allowedContent:
'div(!simplebox); div(!simplebox-content); h2(!simplebox-title)',
requiredContent: 'div(simplebox)',
upcast: function( element ) {
return element.name == 'div' && element.hasClass( 'simplebox' );
}
} );
}
} );

How to force CKEditor to process widgets when setting the HTML of an element

I have create a simple CKEditor widget that highlights the elements that have the class "pink".
I have also added a "Pinkify" button to the toolbar, which replaces the HTML of the selected element with some other elements that have the class "pink".
What I observe when I click the button is that widgets are not created for the freshly inserted elements. However, when I toggle between Source mode and WYSISYG mode, the widgets get created.
See the jsfiddle and its code:
CKEDITOR.replace('ck', {
allowedContent: true,
extraPlugins: 'pink'
});
CKEDITOR.plugins.add('pink', {
requires: 'widget',
init: function(editor) {
editor.widgets.add('pinkwidget', {
upcast: function(element) {
return element.hasClass('pink');
}
});
editor.addCommand('pinkify', {
editorFocus: 1,
exec: function(editor) {
var selection = editor.getSelection(),
selectedElement = selection.getStartElement();
if (selectedElement) {
selectedElement.setHtml("Let's have some <span class=\"pink\">pink</span> widget here!");
editor.widgets.checkWidgets(); // needed?
}
}
});
editor.ui.addButton('pinkify', {
label: 'Pinkify',
command: 'pinkify'
});
},
onLoad: function() {
CKEDITOR.addCss('.cke_widget_pinkwidget { background: pink; }');
}
});
I am aware of this question on Stackoverflow, but I can't get it to work with setHtml called on an element. Can you suggest how to modify the code so that widgets get created as soon as the HTML is updated?
According to the CKEditor team, it is normal that CKEDITOR.dom.element.setHtml does not instanciate widgets (see Widgets not initialised after calling setHtml on an element).
So the workaround they gave me was to rewrite the code that insert HTML in place of the selected element to:
if (selectedElement) {
selectedElement.setHtml("");
editor.insertHtml("Let's have some <span class=\"pink\">pink</span> widget here!");
}
For those like me who didn't know, editor.insertHTML inserts HTML code into the currently selected position in the editor in WYSIWYG mode.
Updated jsFiddle here.

CKEditor: multiple widget templates

I'm currently creating a 'smartobject' widget. In the widgets dialog, the user can choose a 'smartobject', which simply put, generates some html, which should be added to the editor. Here comes the tricky part: the html sometimes div elements and sometimes simply span elements. In the case of the div variant, the widget should be wrapped in a div 'template'. In the case of a span variant, the widget should be wrapped in a span and the html should be added 'inline'.
In the widgets API I see the following way to define a template:
editor.widgets.add('smartobject', {
dialog: 'smartobject',
pathName: lang.pathName,
template: '<div class="cke_smartobject"></div>', // <------
upcast: function(element) {
return element.hasClass('smartObject');
},
init: function() {
this.setData('editorHtml', this.element.getOuterHtml());
},
data: function() {
var editorHtml = this.data.editorHtml;
var newElement = new CKEDITOR.dom.element.createFromHtml(editorHtml);
newElement.copyAttributes(this.element);
this.element.setText(newElement.getText());
}
});
But in my case, the template is more dynamic: sometimes a div and sometimes the span will do the correct thing..
How can I fix this without needing to create two widgets which will do the exact same thing, with only the wrapping element as difference?
I've already tried to replace the entire element in the 'data' method, like:
newElement.replace(this.element);
this.element = newElement;
But this seemed not supported: resulted in undefined errors after calling editor.getData().
I'm using ckeditor v4.5.9
Thanks for your help!
It seems I got it working (with a workaround).
The code:
CKEDITOR.dialog.add('smartobject', this.path + 'dialogs/smartobject.js');
editor.widgets.add('smartobject', {
pathName: lang.pathName,
// This template is needed, to activate the widget logic, but does nothing.
// The entire widgets html is defined and created in the dialog.
template: '<div class="cke_smartobject"></div>',
init: function() {
var widget = this;
widget.on('doubleclick', function(evt) {
editor.execCommand('smartobject');
}, null, null, 5);
},
upcast: function(element) {
return element.hasClass('smartObject');
}
});
// Add a custom command, instead of using the default widget command,
// otherwise multiple smartobject variants (div / span / img) are not supported.
editor.addCommand('smartobject', new CKEDITOR.dialogCommand('smartobject'));
editor.ui.addButton && editor.ui.addButton('CreateSmartobject', {
label: lang.toolbar,
command: 'smartobject',
toolbar: 'insert,5',
icon: 'smartobject'
});
And in the dialog, to insert code looks like:
return {
title: lang.title,
minWidth: 300,
minHeight: 80,
onOk: function() {
var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);
editor.insertElement(element);
// Trigge the setData method, so the widget html is transformed,
// to an actual widget!
editor.setData(editor.getData());
},
...etc.
UPDATE
I made the 'onOk' method a little bit better: the smartobject element is now selected after the insertion.
onOk: function() {
var element = CKEDITOR.dom.element.createFromHtml(smartobjectEditorHtml);
var elementId = "ckeditor-element-" + element.getUniqueId();
element.setAttribute("id", elementId);
editor.insertElement(element);
// Trigger the setData method, so the widget html is transformed,
// to an actual widget!
editor.setData(editor.getData());
// Get the element 'fresh' by it's ID, because the setData method,
// makes the element change into a widget, and thats the element which should be selected,
// after adding.
var refreshedElement = CKEDITOR.document.getById(elementId);
var widgetWrapperElement = CKEDITOR.document.getById(elementId).getParent();
// Better safe then sorry: if the fresh element doesn't have a parent, simply select the element itself.
var elementToSelect = widgetWrapperElement != null ? widgetWrapperElement : refreshedElement;
// Normally the 'insertElement' makes sure the inserted element is selected,
// but because we call the setData method (to ensure the element is transformed to a widget)
// the selection is cleared and the cursor points to the start of the editor.
editor.getSelection().selectElement(elementToSelect);
},
So in short, I partially used the widget API for the parts I wanted:
- Make the html of the widget not editable
- Make it moveable
But I created a custom dialog command, which simply bypasses the default widget insertion, so I can entirely decide my own html structure for the widget.
All seems to work like this.
Any suggestions, to make it better are appreciated:)!
As suggested in this ckeditor forum thread, the best approach would be to set the template to include all possible content elements. Then, in the data function, remove the unnecessary parts according to your specific logic.

syncronized scroll does not work in div based ckeditor

Basically i have 2 instances of ckeditor on a single page. One on the left and another in the right side of the page.
The left editor uses a div rather than traditional iframe. I've done this by removing the iframe plugin and including the div editing area plugin.
The right editor loads in an iframe and but is also div based(i can use the iframe editor as well on the right if required, not an issue).
Now if the cursor/focus is on the right editor's content area then the left editor should scroll along with it. I've tried to use the code as provied by Reinmar in below url but it seems to work only with editors based on iframe on both sides. Also please note that i'm using jquery adapter for initializing the editors.
CKEDITOR how to Identify scroll event
I initialized the editor on left as below:
var editor_left = $( '#editor_left' ).ckeditor();
And below is my code for the right editor:-
var editor_right = $( '#editor_right' ).ckeditor();
editor_right.editor.on( 'contentDom', function() {
var editable = editor_right.editor.editable();
editable.attachListener( editable.getDocument(), 'scroll', function() {
alert('scroll detected');
parent.$('#left_editor_content_area').scrollTop($(this).scrollTop())
});
});
If i use the iframe based editor on the right then i'm able to get the "scroll detected" alert. But the scrollTop() function still does not work as expected
Any help will be appreciated.
The code that you mentioned is right. You got to update scrollTop property of the div-based editable with the scroll position of the window inside the iframe-based editor (JSFiddle).
var editor_div = CKEDITOR.replace( 'editor_div', {
extraPlugins: 'divarea'
} );
CKEDITOR.replace( 'editor_iframe', {
on: {
contentDom: function() {
var editable = this.editable(),
win = this.document.getWindow(),
doc = editable.getDocument();
editable.attachListener( doc, 'scroll', function( evt ) {
// Get scroll position of iframe-based editor.
var scroll = win.getScrollPosition();
// Update scroll position in the div-based editor.
editor_div.editable().$.scrollTop = scroll.y;
} );
}
}
} );

CKEDITOR - how to add permanent onclick event?

I am looking for a way to make the CKEDITOR wysiwyg content interactive. This means for example adding some onclick events to the specific elements. I can do something like this:
CKEDITOR.instances['editor1'].document.getById('someid').setAttribute('onclick','alert("blabla")');
After processing this action it works nice. But consequently if I change the mode to source-mode and then return to wysiwyg-mode, the javascript won't run. The onclick action is still visible in the source-mode, but is not rendered inside the textarea element.
However, it is interesting, that this works fine everytime:
CKEDITOR.instances['editor1'].document.getById('id1').setAttribute('style','cursor: pointer;');
And it is also not rendered inside the textarea element! How is it possible? What is the best way to work with onclick and onmouse events of CKEDITOR content elements?
I tried manually write this by the source-mode:
<html>
<head>
<title></title>
</head>
<body>
<p>
This is some <strong id="id1" onclick="alert('blabla');" style="cursor: pointer;">sample text</strong>. You are using CKEditor.</p>
</body>
</html>
And the Javascript (onclick action) does not work. Any ideas?
Thanks a lot!
My final solution:
editor.on('contentDom', function() {
var elements = editor.document.getElementsByTag('span');
for (var i = 0, c = elements.count(); i < c; i++) {
var e = new CKEDITOR.dom.element(elements.$.item(i));
if (hasSemanticAttribute(e)) {
// leve tlacitko mysi - obsluha
e.on('click', function() {
if (this.getAttribute('class') === marked) {
if (editor.document.$.getElementsByClassName(marked_unique).length > 0) {
this.removeAttribute('class');
} else {
this.setAttribute('class', marked_unique);
}
} else if (this.getAttribute('class') === marked_unique) {
this.removeAttribute('class');
} else {
this.setAttribute('class', marked);
}
});
}
}
});
Filtering (only CKEditor >=4.1)
This attribute is removed because CKEditor does not allow it. This filtering system is called Advanced Content Filter and you can read about it here:
http://ckeditor.com/blog/Upgrading-to-CKEditor-4.1
http://ckeditor.com/blog/CKEditor-4.1-Released
Advanced Content Filter guide
In short - you need to configure editor to allow onclick attributes on some elements. For example:
CKEDITOR.replace( 'editor1', {
extraAllowedContent: 'strong[onclick]'
} );
Read more here: config.extraAllowedContent.
on* attributes inside CKEditor
CKEditor encodes on* attributes when loading content so they are not breaking editing features. For example, onmouseout becomes data-cke-pa-onmouseout inside editor and then, when getting data from editor, this attributes is decoded.
There's no configuration option for this, because it wouldn't make sense.
Note: As you're setting attribute for element inside editor's editable element, you should set it to the protected format:
element.setAttribute( 'data-cke-pa-onclick', 'alert("blabla")' );
Clickable elements in CKEditor
If you want to observe click events in editor, then this is the correct solution:
editor.on( 'contentDom', function() {
var element = editor.document.getById( 'foo' );
editor.editable().attachListener( element, 'click', function( evt ) {
// ...
// Get the event target (needed for events delegation).
evt.data.getTarget();
} );
} );
Check the documentation for editor#contentDom event which is very important in such cases.

Resources