CKEditor: Collapsing only 2nd+ toolbar rows - howto

Normally CKEditor (v3.5.2) hides/shows all the toolbar buttons when you press the collapse/expand button but I needed to always show the first row with "basic tools" and only collapse the second and following rows with advanced functionality tool buttons. CKEditor doesn't have proper support for that but there is a simple workaround.

Update: Example solution (CKEditor 3.6.1) published, see the changes done or download the full source and open _samples/replacebyclass.html.

Introduction

The trick is to override the command toolbarCollapse not to hide the complete toolbox but only specific toolbars. Let's see what the toolbox looks like:



The whole thing you can see on the image is the toolbox, which contains the following elements:
  • 1 & 2: a list of toolbars including optional line breaks to spread the toolbars across multiple lines; a toolbar may contain one or more buttons, such as (B I U) in the toolbar marked as #4
  • Collapser - the button used to collapse and expand the toolbox
The (generated) source code is something like this:


<td class="cke_top" id="cke_top_newedittextarea">
  <div class="cke_toolbox">
    <span id="cke_6" class="cke_voice_label">Toolbar</span>
    <span id="cke_7" class="cke_toolbar">...</span>
    ...
    <div class="cke_break">
    <span id="cke_29" class="cke_toolbar">>...</span>
    ...
  </div>
  <a id="cke_61" class="cke_toolbox_collapser" title="Collapse Toolbar">▲</a>
</td>



The CKEditor's tool area, represented by a td, contains the toolbars contained in a div.cke_toolbox (#2) and a link representing the collapser (#10). The toolbox contains various elements, mostly span.cke_toolbar, representing the individual toolbars (such as #4 above), and some special ones such as div.cke_break (#6), which splits the toolbars into multiple lines.

Implementation

To hide only the second and following rows, we will go through the list of toolbox' children and hide() each starting with the first div.cke_break. Add the following into your CKEditor’s configuration:


config.toolbarStartupExpanded = false;


and add there or somewhere else also the following:


/**
 * Override the default 'toolbarCollapse' command to hide
 * only toolbars in the row two and onwards.
 */
CKEDITOR.on('instanceReady', function(e) {

function switchVisibilityAfter1stRow(toolbox, show) { var inFirstRow = true; var elements = toolbox.getChildren(); var elementsCount = elements.count(); var elementIndex = 0; var element = elements.getItem(elementIndex); for (; elementIndex < elementsCount; element = elements.getItem(++elementIndex)) { inFirstRow = inFirstRow && !(element.is('div') && element.hasClass('cke_break'));

if (!inFirstRow) { if (show) element.show(); else element.hide(); } } }

var editor = e.editor; var collapser = (function() { try { // We've HTML: td.cke_top { // div.cke_toolbox {span.cke_toolbar, ... } // , a.cke_toolbox_collapser } var firstToolbarId = editor.toolbox.toolbars[0].id; var firstToolbar = CKEDITOR.document.getById(firstToolbarId); var toolbox = firstToolbar.getParent(); var collapser = toolbox.getNext(); return collapser; } catch (e) {} })();

// Copied from editor/_source/plugins/toolbar/plugin.js & modified editor.addCommand( 'toolbarCollapse', {

exec : function( editor ) { if (collapser == null) return;

var toolbox = collapser.getPrevious(), contents = editor.getThemeSpace( 'contents' ), toolboxContainer = toolbox.getParent(), contentHeight = parseInt( contents.$.style.height, 10 ), previousHeight = toolboxContainer.$.offsetHeight,

collapsed = toolbox.hasClass('iterate_tbx_hidden');//!toolbox.isVisible();

if ( !collapsed ) { switchVisibilityAfter1stRow(toolbox, false); // toolbox.hide(); toolbox.addClass('iterate_tbx_hidden'); if (!toolbox.isVisible()) toolbox.show(); // necessary 1st time if initially collapsed

collapser.addClass( 'cke_toolbox_collapser_min' ); collapser.setAttribute( 'title', editor.lang.toolbarExpand ); } else { switchVisibilityAfter1stRow(toolbox, true); // toolbox.show(); toolbox.removeClass('iterate_tbx_hidden');

collapser.removeClass( 'cke_toolbox_collapser_min' ); collapser.setAttribute( 'title', editor.lang.toolbarCollapse ); }

// Update collapser symbol. collapser.getFirst().setText( collapsed ? '\u25B2' : // BLACK UP-POINTING TRIANGLE '\u25C0' ); // BLACK LEFT-POINTING TRIANGLE

var dy = toolboxContainer.$.offsetHeight - previousHeight; contents.setStyle( 'height', ( contentHeight - dy ) + 'px' );

editor.fire( 'resize' ); },

modes : { wysiwyg : 1, source : 1 } } )

// Make sure advanced toolbars initially collapsed editor.execCommand( 'toolbarCollapse' ); });


Explanation:
  • General: When the editor finishes its initialization, i.e. on the instanceReady event, we override the default toolbarCollapse command, defined in the toolbar plugin
  • #07 function switchVisibilityAfter1stRow: A utility function to hide/show the toolbars that follow the first row of toolbars (including the line-break element)
    • #16 We start hiding/showing with the first div element having the class cke_break, which CKEditor uses to force line breaks
    • #20 The actual change of visibility
    • Notice that the elements are instances of CKEditor.dom.element
  • #26 collapser lookup: It's little complicated to locate the collapser for we have no way of learning its ID. Fortunately the editor object has some kind of object representation of its toolbox and toolbars. These are not the CKEditor.dom.elements we need but contain the actual ids, so we can get the id of a toolbar, use it to get its dom.element, and get the parent toolbox and its sibling collapser.
  • #43 the actual re-definition of the toolbarCollapse command:
    • Only the highlighted lines have been added or changed, the rest is copied from the toolbar plugin.
    • #48 We use the collapser element located above instead of finding it by its (unknown to us) id, as the original function did.
    • #56, #61,70: We use a custom class - iterate_tbx_hidden - to make it easy to detect the current state.
    • #62: A trick to show the toolbar hidden by having set config.toolbarStartupExpanded to false, explained later
  • #94 executing the command to display only the first toolbars row: explained below

Trick: Not hiding visible buttons while the user looks

We want to have the "advanced" buttons initially collapsed. If you tried to achieve it simply by calling editor.execCommand( 'toolbarCollapse' ) at the end of the instanceReady event handler then the user would initially see all rows, which would be then collapsed. This is little strange experience, we would prefer to hide the advanced rows before they are displayed. This is how to do it:
  1. Tell CKEditor to initially collapse the toolbox (config.toolbarStartupExpanded=false). This happens before the instanceReady event and thus it executes the original toolbarCollapse command, which works by invoking hide() on the toolbar container (div.cke_toolbox). So all buttons will be hidden.
  2. However our new toolbarCollapse, which we invoke after instanceReady, will believe that the toolbox is not collapsed before it won't find the special marker class iterate_tbx_hidden on the toolbox. Little strangely it will anyway do exactly what we want: hide the 2nd row of buttons and set the collapser to the collapsed position (actually it just keeps it). We need only one thing more and that is to show the currently hidden content of the toolbox (line #62).

Conclusion

It is little cumbersome to make it possible to collapse/expand only the 2nd+ rows but it is possible. There are certainly other - and better - ways to do it but this worked fine for me.

Tags: webdev JavaScript library


Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob