I'm having trouble adding classes to selected image in ckeditor. What I came up with is this http://pokit.org/get/img/8d89802e1d6f6371f5bc326898d8b414.jpg.
I added 2 buttons for selecting whether whether a picture is in portrait or landscape mode. You can select either of them or none, and add costum height/width.
Here is my code:
CKEDITOR.replace('maindesc', {
"extraPlugins": "imgbrowse",
"filebrowserImageBrowseUrl": "/ckeditor/plugins/imgbrowse",
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
img: function( el ) {
// Add an attribute.
if ( !el.attributes.alt ) {
el.attributes.alt = 'Img';
el.addClass('ckeditorImg');
if (Landscape == 1) {
el.addClass('ckLandscape');
el.attributes['style'] = '';
}
else if (Portrait == 1) {
el.addClass('ckPortrait');
el.attributes['style'] = '';
}
}
}
}
} );
}
}
});
So as far as I understand this goes through all, so I wrote that if the image has no alt attribute to add one and add the classes I want. Unfortunately this approach doesn't allow me to change the class on selected image when a user wants to change it, but instead he has to delete the image, select it again and then choose class.
My question is whether there is a way to get to currently selected image instead of going through all <img> tags in ckeditor and change its class.
Here is an example for how to add a new button to ckeditor that is enabled/disables based on the element that you currently select and add a class to that specific element (in this example it's for images, however you can use it in any way you want).
// Set the callback function
var setLandscapeClass = {
exec: function(editor) {
editor.getSelection().getStartElement().addClass('ckLandscape')
}
}
//Create the plugin
CKEDITOR.plugins.add('setLandscapeClass', {
init: function(editor) {
editor.addCommand('setLandscapeClass', setLandscapeClass);
editor.ui.addButton("setLandscapeClass", {
label: 'Set Landscape Class',
icon: '',
command: 'setLandscapeClass'
});
}
});
// Create the instance and add the plugin
CKEDITOR.replace( 'editor1', {
extraPlugins: 'setLandscapeClass',
allowedContent: true
});
// enable/disable the button based on the selection of the text in the editor
CKEDITOR.instances.editor1.on( 'selectionChange', function( evt ) {
var landscapeButton = this.getCommand( 'setLandscapeClass' );
if ( evt.data.path.lastElement.is( 'img' ) ) {
landscapeButton.enable();
} else {
landscapeButton.disable();
}
});
You can see a working demo here:
https://jsfiddle.net/7nm9q1qv/
I only created 1 button, and there is no icon there. I think you can use that code to create also the second button (for portrait class).
Update - add item to the context menu
In order to add a new item to the context-menu you should add this code:
// Add the context-menu
if (editor.addMenuItem) {
editor.addMenuGroup('testgroup');
editor.addMenuItem('setLandscapeItem', {
label: 'Set landscape class',
command: 'setLandscapeClass',
group: 'testgroup'
});
}
// On contextmenu - set the item as "visible" by the menu
if (editor.contextMenu) {
editor.contextMenu.addListener(function(element, selection) {
if (element.hasClass('ckLandscape') === false) {
return { setLandscapeItem: CKEDITOR.TRISTATE_ON };
}
});
}
Inside the init function of the plugin you add.
You can see that I added this line:
if (element.hasClass('ckLandscape') === false) {
(Which you can remove) only to give you an example of how to show the item in the menu only if the ckLandscape class doesn't exists for this image.
The updated version of the jsfiddle is here:
https://jsfiddle.net/7nm9q1qv/1/
Related
I'm using CKEditor version 3.6
I want to automatically add class="newsleft" to any image tag added through the WYSIWYG.
I've seen a few posts that mention dataProcessor but have no idea which file this should be added or how to do it.
Can someone tell me where I would place the following code?
editor.dataProcessor.htmlFilter.addRules(
{
elements:
{
img: function( element )
{
if ( !element.attributes.alt )
element.attributes.alt = 'An image';
}
}
} );
Basically put it in instanceReady listener and it will be fine (3.x and 4.x) (fiddle):
CKEDITOR.replace( 'editor', {
plugins: 'wysiwygarea,toolbar,sourcearea,image,basicstyles',
on: {
instanceReady: function() {
this.dataProcessor.htmlFilter.addRules( {
elements: {
img: function( el ) {
// Add an attribute.
if ( !el.attributes.alt )
el.attributes.alt = 'An image';
// Add some class.
el.addClass( 'newsleft' );
}
}
} );
}
}
} );
CKEDITOR.htmlParser.element.addClass is available since CKEditor 4.4. You can use this.attributes[ 'class' ] prior to that version.
Here's another approach:
CKEDITOR.on( 'instanceReady', function( evt ) {
evt.editor.dataProcessor.htmlFilter.addRules( {
elements: {
img: function(el) {
el.addClass('img-responsive');
}
}
});
});
this worked for me in 3.6
add the following code to config.js
CKEDITOR.on('dialogDefinition', function (ev) {
// Take the dialog name and its definition from the event data.
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
// Check if the definition is image dialog window
if (dialogName == 'image') {
// Get a reference to the "Advanced" tab.
var advanced = dialogDefinition.getContents('advanced');
// Set the default value CSS class
var styles = advanced.get('txtGenClass');
styles['default'] = 'newsleft';
}
});
Because of this topic is being found in Google very high, I might help people by answering the question: how to add a default class when inserting an image in CKeditor. This answer is for CKeditor 4.5.1. as this is the latest version right now.
Look up image.js in /ckeditor/plugins/image/dialogs/image.js
Search for d.lang.common.cssClass
You will find: d.lang.common.cssClass,"default":""
Edit it with your class name(s) such as: d.lang.common.cssClass,"default":"img-responsive"
I've tried this and it works!
in Version: 4.5.3
Look up image.js in /ckeditor/plugins/image/dialogs/image.js
Search for editor.lang.common.cssClass
You will find: editor.lang.common.cssClass,"default":""
Edit it with your class name(s) such as: editor.lang.common.cssClass,"default":"your-class-name"
I have a plugin that im making use of called content.js http://innovastudio.com/content-builder.aspx
Im adding in dynamic divs to the page which I would like to have the content.js plugin assigned to it, so I can make use of its functionality.
On a single div, or already defined div within the page, I dont appear to have any issues with multiple divs.
However if I add in a div with the same class, I cant seem to bind the plugin to it.
Ive included the code for instantiating the div with the contentbuilder plugin, but I wondering if there is a way to bind it to new elements that are added to the page with the class of "letter". Or if there is a generic way of binding plugins to divs using jquery.
$('div.letter').contentbuilder({
enableZoom:false,
snippetOpen: true,
imageselect: 'images.html',
fileselect: 'images.html',
snippetFile: '/assets/templates/content-builder/default/snippets.html',
toolbar: 'left',
//sourceEditor: false,
onDrop:function(){
// function for when an item is dragged into the editable area
},
onRender: function () {
var coverLength = $("#coverpage div.row").length;
var mainContent = $("#maincontent div.row").length;
if(coverLength == 0)
{
$("#coverpage").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#coverpage div.no-content-on-page").remove();
}
if(mainContent == 0)
{
$("#maincontent").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#maincontent div.no-content-on-page").remove();
}
//custom script here
}
});
If you must add these divs in a dinamic way, i think that you should init the plugin for each time that you add a new div. To avoid init same div twice, use some class like in the following example:
function createLetter(){
$("body").append('<div class="letter mustBeActivated"></div>');
initContentBuilder();
}
function initContentBuilder(){
$('div.letter.mustBeActivated').contentbuilder({
enableZoom:false,
snippetOpen: true,
imageselect: 'images.html',
fileselect: 'images.html',
snippetFile: '/assets/templates/content-builder/default/snippets.html',
toolbar: 'left',
//sourceEditor: false,
onDrop:function(){
// function for when an item is dragged into the editable area
},
onRender: function () {
var coverLength = $("#coverpage div.row").length;
var mainContent = $("#maincontent div.row").length;
if(coverLength == 0)
{
$("#coverpage").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#coverpage div.no-content-on-page").remove();
}
if(mainContent == 0)
{
$("#maincontent").html('<div class="no-content-on-page">Select your content from the right sidebar</div>')
}
else
{
$("#maincontent div.no-content-on-page").remove();
}
//custom script here
}
}).removeClass('mustBeActivated');
}
I am using the following code to convert unoredered html list into a select drop down list:
jQuery(document).ready( function($) {
//build dropdown - main navigation
$("<select />").appendTo(".region-menu-inner nav");
// Create default option "Go to..."
$("<option />", {
"selected": "selected",
"value" : "",
"text" : "Navigate..."
}).appendTo("nav select");
// Populate dropdowns with the first menu items
$(".region-menu-inner li a").each(function() {
var el = $(this);
$("<option />", {
"value" : el.attr("href"),
"text" : el.text()
}).appendTo(".region-menu-inner select");
});
//make responsive dropdown menu actually work
$(".region-menu-inner select").change(function() {
window.location = $(this).find("option:selected").val();
});
});
At the same time, I am using Simple dialog module for Drupal to create modular window. This module comes with only one js file. The code this module is using is below:
/*
#file
Defines the simple modal behavior
*/
(function ($) {
/*
Add the class 'simple-dialog' to open links in a dialog
You also need to specify 'rev="<selector>"' where the <selector>
is the unique id of the container to load from the linked page.
Any additional jquery ui dialog options can be passed through
the rel tag using the format:
rel="<option_name1>:<value1>;<option_name2>:<value2>;"
e.g. <a href="financing/purchasing-options" class="simple-dialog"
rel="width:900;resizable:false;position:[60,center]"
rev="content-area" title="Purchasing Options">Link</a>
NOTE: This method doesn't not bring javascript files over from
the target page. You will need to make sure your javascript is
either inline in the html that's being loaded, or in the head tag
of the page you are on.
ALSO: Make sure the jquery ui.dialog library has been added to the page
*/
Drupal.behaviors.simpleDialog = {
attach: function (context, settings) {
// Create a container div for the modal if one isn't there already
if ($("#simple-dialog-container").length == 0) {
// Add a container to the end of the body tag to hold the dialog
$('body').append('<div id="simple-dialog-container" style="display:none;"></div>');
try {
// Attempt to invoke the simple dialog
$( "#simple-dialog-container", context).dialog({
autoOpen: false,
modal: true,
close: function(event, ui) {
// Clear the dialog on close. Not necessary for your average use
// case, butis useful if you had a video that was playing in the
// dialog so that it clears when it closes
$('#simple-dialog-container').html('');
}
});
var defaultOptions = Drupal.simpleDialog.explodeOptions(settings.simpleDialog.defaults);
$('#simple-dialog-container').dialog('option', defaultOptions);
}
catch (err) {
// Catch any errors and report
Drupal.simpleDialog.log('[error] Simple Dialog: ' + err);
}
}
// Add support for custom classes if necessary
var classes = '';
if (settings.simpleDialog.classes) {
classes = ', .' + settings.simpleDialog.classes;
}
$('a.simple-dialog' + classes, context).each(function(event) {
if (!event.metaKey && !$(this).hasClass('simpleDialogProcessed')) {
// Add a class to show that this link has been processed already
$(this).addClass('simpleDialogProcessed');
$(this).click(function(event) {
// prevent the navigation
event.preventDefault();
// Set up some variables
var url = $(this).attr('href');
// Use default title if not provided
var title = $(this).attr('title') ? $(this).attr('title') : settings.simpleDialog.title;
if (!title) {
title = $(this).text();
}
// Use defaults if not provided
var selector = $(this).attr('name') ? $(this).attr('name') : settings.simpleDialog.selector;
var options = $(this).attr('rel') ? Drupal.simpleDialog.explodeOptions($(this).attr('rel')) : Drupal.simpleDialog.explodeOptions(settings.simpleDialog.defaults);
if (url && title && selector) {
// Set the custom options of the dialog
$('#simple-dialog-container').dialog('option', options);
// Set the title of the dialog
$('#simple-dialog-container').dialog('option', 'title', title);
// Add a little loader into the dialog while data is loaded
$('#simple-dialog-container').html('<div class="simple-dialog-ajax-loader"></div>');
// Change the height if it's set to auto
if (options.height && options.height == 'auto') {
$('#simple-dialog-container').dialog('option', 'height', 200);
}
// Use jQuery .get() to request the target page
$.get(url, function(data) {
// Re-apply the height if it's auto to accomodate the new content
if (options.height && options.height == 'auto') {
$('#simple-dialog-container').dialog('option', 'height', options.height);
}
// Some trickery to make sure any inline javascript gets run.
// Inline javascript gets removed/moved around when passed into
// $() so you have to create a fake div and add the raw data into
// it then find what you need and clone it. Fun.
$('#simple-dialog-container').html( $( '<div></div>' ).html( data ).find( '#' + selector ).clone() );
// Attach any behaviors to the loaded content
Drupal.attachBehaviors($('#simple-dialog-container'));
});
// Open the dialog
$('#simple-dialog-container').dialog('open');
// Return false for good measure
return false;
}
});
}
});
}
}
// Create a namespace for our simple dialog module
Drupal.simpleDialog = {};
// Convert the options to an object
Drupal.simpleDialog.explodeOptions = function (opts) {
var options = opts.split(';');
var explodedOptions = {};
for (var i in options) {
if (options[i]) {
// Parse and Clean the option
var option = Drupal.simpleDialog.cleanOption(options[i].split(':'));
explodedOptions[option[0]] = option[1];
}
}
return explodedOptions;
}
// Function to clean up the option.
Drupal.simpleDialog.cleanOption = function(option) {
// If it's a position option, we may need to parse an array
if (option[0] == 'position' && option[1].match(/\[.*,.*\]/)) {
option[1] = option[1].match(/\[(.*)\]/)[1].split(',');
// Check if positions need be converted to int
if (!isNaN(parseInt(option[1][0]))) {
option[1][0] = parseInt(option[1][0]);
}
if (!isNaN(parseInt(option[1][1]))) {
option[1][1] = parseInt(option[1][1]);
}
}
// Convert text boolean representation to boolean
if (option[1] === 'true') {
option[1]= true;
}
else if (option[1] === 'false') {
option[1] = false;
}
return option;
}
Drupal.simpleDialog.log = function(msg) {
if (window.console) {
window.console.log(msg);
}
}
})(jQuery);
Link that is using this module, in the source looks like this:
<a href='/user' name='user-login' id='user-login' class='simple-dialog' title='Login ' rel='width:400;resizable:false;position:[center,60]'>Log in</a>
The problem is that when you click on that link, it takes a second or two to load the popup and when it actually loads, second set of select dropdown list is being generated. If you click login link one more time, it generates third select list. Basically it duplicates whatever is converted from ul li into select list.
Thanks for help in advance.
jQuery(document).ready( function($) {
$(".region-menu-inner nav").empty(); //empty here
//build dropdown - main navigation
$("<select />").appendTo(".region-menu-inner nav");
// Create default option "Go to..."
$("<option />", {
"selected": "selected",
"value" : "",
"text" : "Navigate..."
}).appendTo("nav select");
// Populate dropdowns with the first menu items
$(".region-menu-inner li a").each(function() {
var el = $(this);
$("<option />", {
"value" : el.attr("href"),
"text" : el.text()
}).appendTo(".region-menu-inner select");
});
//make responsive dropdown menu actually work
$(".region-menu-inner select").change(function() {
window.location = $(this).find("option:selected").val();
});
});
I'm working on a TinyMCE plugin and one thing I want it to do is register commands/buttons that toggle custom formatting.
For example if you click the bold button in TinyMCE it will show the bold button highlighted while in bold text. Digging into the source code I see this happens via: tinymce.EditorCommands.addCommands thought I can't seem to figure out how to duplicate it. The documentation of TinyMCE is just horrible as well =(
So given customFormat I want to be able to have a button setup by my plugin that when the customFormat is applied it shows as such like the Bold, Italics, and other such buttons do on the toolbar. And clicking on my customFormat toggles that format on/off. I can easily accomplish the toogle via "addCommand" and "addButton" but then it does not have state tracking like Bold and others do.
Showing my current non-working attempt (this code is inside init of my plugin create method):
tinymce.EditorCommands.call('addCommands', {
'MyFormat' : function(name) {
ed.formatter.toggle("customFormat");
}
},'exec');
tinymce.EditorCommands.call('addCommands', {
'MyFormat' : function(name) {
return ed.formatter.match('customFormat');
}
},'state');
ed.addButton('customformat', {cmd : 'MyFormat'});
And here is the link to the "documentation" of addCommands:
http://www.tinymce.com/wiki.php/API3:method.tinymce.EditorCommands.addCommands
After a lot more looking around I found this which seems to be perfect:
http://www.tinymce.com/wiki.php/API3:method.tinymce.Editor.addQueryStateHandler
But when I implement the code it doesn't change the state of the button:
ed.addCommand('MyFormat', function(ui, v) {
ed.formatter.toggle("thoughtFormat");
});
ed.addQueryStateHandler('MyFormat', function() {
return ed.formatter.match('thoughtFormat');
});
ed.addButton('myformat', {cmd : 'MyFormat'});
In case someone doesn't want to do it the 'plug-in' way, here's the guide for TinyMCE 4.x.
First of all, you need to define a custom format:
formats: {
custom_format: {inline: 'span', styles: {color: "red"}, attributes: {class: 'some_css_class'}}
}
Then you'll have to add a button to your toolbar:
toolbar: "mybutton",
Next, you need to setup your button, so that it toggles the format:
setup: function(editor) {
editor.addButton('mybutton', {
text: 'My button',
icon: false,
onclick: function() {
tinymce.activeEditor.formatter.toggle('custom_format')
}
});
}
Furthermore, if you want the editor to set the state of the button to indicate the format of current node, automatically, add this to setup function:
onPostRender: function() {
var ctrl = this;
editor.on('NodeChange', function(e) {
ctrl.active(e.element.className == "some_css_class")
});
}
Your tinymce.init function should look like this:
tinymce.init({
selector: "textarea",
formats: {
// Other formats...
custom_format: {inline: 'span', styles: {color: "red"}, attributes: {class: 'some_css_class'}}
}
// Other default toolbars
toolbar_n: "mybutton",
// Finally, setup your button
setup: function(editor) {
editor.addButton('mybutton', {
text: 'My Button',
icon: false,
onclick: function() {
tinymce.activeEditor.formatter.toggle('custom_format')
},
onPostRender: function() {
var ctrl = this;
editor.on('NodeChange', function(e) {
ctrl.active(e.element.className == "some_css_class")
});
}
});
}
Note that class attribute I added to my custom format. This approach made it possible for me define my custom styles in a separate stylesheet file and keep my markup as dry as possible (No inline styling!). Point content_css option to your stylesheet and you'll be good to go.
However, due to fact that I'm using Rails as back-end and BatmanJS as front-end (and I'm fairly new to the latter), I couldn't figure out how assets routing works, and ended up adding my custom styles to default content stylesheet file of tinyMCE skin itself (located at skins/SKIN_NAME/content.min.css).
Thanks to Thariama for insights that allowed me to dig deeper finally figuring out how to do this. I'm not sure its the "right way" but as I said TinyMCE has the worst documentation imaginable.
The key for me was to make an hook the onNodeChange event, using the setActive trick. Full example plugin with a custom button that activates when that format is present wherever the cursor is:
(function() {
tinymce.create('tinymce.plugins.CoolPlugin', {
init : function(ed, url) {
ed.addCommand('MyFormat', function(ui, v) {
ed.formatter.toggle("myFormat");
});
ed.addButton("coolformat", {
title : 'MyFormat Tooltip',
cmd : 'MyFormat',
image: url + '/coolformat.png',
});
ed.onNodeChange.add(function(ed, cm, n) {
active = ed.formatter.match('myFormat');
control = ed.controlManager.get('coolformat').setActive(active);
});
ed.onInit.add(function(ed, e) {
ed.formatter.register('myFormat',
{inline: 'span', classes : ['cool'] } );
});
}
});
// Register plugin
tinymce.PluginManager.add('cool', tinymce.plugins.CoolPlugin);
})();
Here is an example:
ed.controlManager.get('my_control_element').setActive(true); // could be bold or whatever
I want to add a new menu item to TinyMCE's context menu, and perform a command when the user clicks it, so far i have this, which is not working:
tinyMCE.init({
...
setup : function(ed) {
ed.onContextMenu.add(function(ed, menu) {
menu.add({title : 'Menu 1', onclick : function() {
alert('Item 1 was clicked.');
}});
});
}
The code above throws an error saying "menu.add is not a function", if i remove the menu.add stuff and place a console.log(menu), it returns "contextmenu" upon opening the context menu.
What would be the right way to add an item to the context menu ? Preferably without having to modify the plugin itself. Thanks in advance.
You will need something like
ed.onContextMenu.add(function(ed, e) {
if (!e.ctrlKey) {
// Restore the last selection since it was removed
if (lastRng)
ed.selection.setRng(lastRng);
var menu = this._getMenu(ed);
if ((typeof menu).toLowerCase() == 'object')
{
menu.showMenu(e.clientX, e.clientY);
Event.add(ed.getDoc(), 'click', function(e) {
hide(ed, e);
});
Event.cancel(e);
}
}
});
and the function _getMenu where you may insert contextmenu options:
//example this will only display if an image was clicked
if (node !== "undefined" && node.nodeName.toLowerCase() == 'img') {
m.add({
title: 'my menu',
});
m.addSeparator();
// Inline-Element editieren
m.add({
title: 'to be choosen1',
icon: 'http://...',
cmd: 'undo'
});
t.onContextMenu.dispatch(t, m, el, col);
return m;
}
EDIT:
you can get the default menu using (the plugin contextmenu needs to be active)
var editor = tinymce.get(editor_id);
var menu = editor.plugins.contextmenu._getMenu(editor);
adding an entry to the menu should work as follows
menu.add({title : 'undo', icon : 'undo', cmd : 'Undo'});
it might be necessary to render the menu explicitly using showMenu.
Another way to insert a menu to the contextemenu is to modify the editor_plugin.js in the tiny_mce/plugins/contextemneu directory and add the entry directly. You may also copy the plugin, modify and rename it - let it work as a custom plugin.
You can add context menu like this:
setup : function(ed) {
ed.onContextMenu.add(function(ed, menu) {
displayContextMenu(ed,e);
}});
});
}
function displayContextMenu(ed,e){
var m = ed.plugins.contextmenu._getMenu(ed);
m.add({title : 'advanced.bold_desc', icon : 'bold', cmd : 'bold'});
}