1 /* global ToolbarConfigurator, alert */
6 var AbstractToolbarModifier
= ToolbarConfigurator
.AbstractToolbarModifier
;
9 * @class ToolbarConfigurator.ToolbarModifier
10 * @param {String} editorId An id of modified editor
12 * @extends AbstractToolbarModifier
15 function ToolbarModifier( editorId
, cfg
) {
16 AbstractToolbarModifier
.call( this, editorId
, cfg
);
18 this.removedButtons
= null;
19 this.originalConfig
= null;
20 this.actualConfig
= null;
21 this.emptyVisible
= false;
23 // edit, paste, config
26 this.toolbarButtons
= [
29 active: 'Hide empty toolbar groups',
30 inactive: 'Show empty toolbar groups'
34 cssClass: 'button-a-soft',
35 clickCallback: function( button
, buttonDefinition
) {
36 var className
= 'button-a-background';
38 button
[ button
.hasClass( className
) ? 'removeClass' : 'addClass' ]( className
);
40 this._toggleVisibilityEmptyElements();
42 if ( this.emptyVisible
) {
43 button
.setText( buttonDefinition
.text
.active
);
45 button
.setText( buttonDefinition
.text
.inactive
);
50 text: 'Add row separator',
53 cssClass: 'button-a-soft',
54 clickCallback: function() {
62 clickCallback: function() {
65 this.modifyContainer.addClass( 'hidden' );
66 this.configContainer.removeClass( 'hidden' );
67 this.configContainer.setHtml( '<textarea></textarea>' );
68 this.showToolbarBtnsByGroupName( 'config' );
72 text: 'Select config',
75 cssClass: 'button-a-soft',
76 clickCallback: function() {
77 this.configContainer
.findOne( 'textarea' ).$.select();
81 text: 'Back to configurator',
84 cssClass: 'button-a-background',
85 clickCallback: function() {
86 if ( this.state
=== 'paste' ) {
87 var cfg
= this.configContainer
.findOne( 'textarea' ).getValue();
88 cfg
= ToolbarModifier
.evaluateToolbarGroupsConfig( cfg
);
91 this.setConfig( cfg
);
93 alert( 'Your pasted config is wrong.' );
98 this._showConfigurationTool();
99 this.showToolbarBtnsByGroupName( this.state
);
103 text: 'Get toolbar <span class="highlight">config</span>',
106 cssClass: 'button-a-background icon-pos-left icon-download',
107 clickCallback: function() {
108 this.state
= 'config';
110 this.showToolbarBtnsByGroupName( this.state
);
115 this.cachedActiveElement
= null;
119 ToolbarConfigurator
.ToolbarModifier
= ToolbarModifier
;
121 ToolbarModifier
.prototype = Object
.create( ToolbarConfigurator
.AbstractToolbarModifier
.prototype );
126 ToolbarModifier
.prototype.getActualConfig = function() {
127 var copy
= AbstractToolbarModifier
.prototype.getActualConfig
.call( this );
129 if ( copy
.toolbarGroups
) {
131 var max
= copy
.toolbarGroups
.length
;
132 for ( var i
= 0; i
< max
; i
+= 1 ) {
133 var currentGroup
= copy
.toolbarGroups
[ i
];
135 copy
.toolbarGroups
[ i
] = ToolbarModifier
.parseGroupToConfigValue( currentGroup
);
144 * @param {Function} callback
145 * @param {String} [config]
146 * @param {Boolean} [forceKeepRemoveButtons=false]
149 ToolbarModifier
.prototype._onInit = function( callback
, config
, forceKeepRemoveButtons
) {
150 forceKeepRemoveButtons
= ( forceKeepRemoveButtons
=== true );
151 AbstractToolbarModifier
.prototype._onInit
.call( this, undefined, config
);
153 this.removedButtons
= [];
155 if ( forceKeepRemoveButtons
) {
156 if ( this.actualConfig
.removeButtons
) {
157 this.removedButtons
= this.actualConfig
.removeButtons
.split( ',' );
159 this.removedButtons
= [];
162 if ( !( 'removeButtons' in this.originalConfig
) ) {
163 this.originalConfig
.removeButtons
= '';
164 this.removedButtons
= [];
166 this.removedButtons
= this.originalConfig
.removeButtons
? this.originalConfig
.removeButtons
.split( ',' ) : [];
170 if ( !this.actualConfig
.toolbarGroups
)
171 this.actualConfig
.toolbarGroups
= this.fullToolbarEditor
.getFullToolbarGroupsConfig();
173 this._fixGroups( this.actualConfig
);
174 this._calculateTotalBtns();
176 this._createModifier();
177 this._refreshMoveBtnsAvalibility();
178 this._refreshBtnTabIndexes();
180 if ( typeof callback
=== 'function' )
181 callback( this.mainContainer
);
187 ToolbarModifier
.prototype._showConfigurationTool = function() {
188 this.configContainer
.addClass( 'hidden' );
189 this.modifyContainer
.removeClass( 'hidden' );
193 * Show configuration file in tool
197 ToolbarModifier
.prototype._showConfig = function() {
199 actualConfig
= this.getActualConfig(),
201 if ( actualConfig
.toolbarGroups
) {
202 cfg
.toolbarGroups
= actualConfig
.toolbarGroups
;
204 var groups
= prepareGroups( actualConfig
.toolbarGroups
, this.cfg
.trimEmptyGroups
);
206 cfg
.toolbarGroups
= '\n\t\t' + groups
.join( ',\n\t\t' );
209 function prepareGroups( toolbarGroups
, trimEmptyGroups
) {
211 max
= toolbarGroups
.length
;
213 for ( var i
= 0; i
< max
; i
++ ) {
214 var group
= toolbarGroups
[ i
];
216 if ( group
=== '/' ) {
217 groups
.push( '\'/\'' );
221 if ( trimEmptyGroups
) {
222 var max2
= group
.groups
.length
;
224 var subgroup
= group
.groups
[ max2
];
226 if ( ToolbarModifier
.getTotalSubGroupButtonsNumber( subgroup
, that
.fullToolbarEditor
) === 0 ) {
227 group
.groups
.splice( max2
, 1 );
232 if ( !( trimEmptyGroups
&& group
.groups
.length
=== 0 ) ) {
233 groups
.push( AbstractToolbarModifier
.stringifyJSONintoOneLine( group
, {
244 if ( actualConfig
.removeButtons
) {
245 cfg
.removeButtons
= actualConfig
.removeButtons
;
249 '<textarea class="configCode" readonly>',
250 'CKEDITOR.editorConfig = function( config ) {\n',
251 ( cfg
.toolbarGroups
? '\tconfig.toolbarGroups = [' + cfg
.toolbarGroups
+ '\n\t];' : '' ),
252 ( cfg
.removeButtons
? '\n\n' : '' ),
253 ( cfg
.removeButtons
? '\tconfig.removeButtons = \'' + cfg
.removeButtons
+ '\';' : '' ),
260 this.modifyContainer
.addClass( 'hidden' );
261 this.configContainer
.removeClass( 'hidden' );
263 this.configContainer
.setHtml( content
);
267 * Toggle empty groups and subgroups visibility.
271 ToolbarModifier
.prototype._toggleVisibilityEmptyElements = function() {
272 if ( this.modifyContainer
.hasClass( 'empty-visible' ) ) {
273 this.modifyContainer
.removeClass( 'empty-visible' );
274 this.emptyVisible
= false;
276 this.modifyContainer
.addClass( 'empty-visible' );
277 this.emptyVisible
= true;
280 this._refreshMoveBtnsAvalibility();
284 * Creates HTML main container of modifier.
286 * @returns {CKEDITOR.dom.element}
289 ToolbarModifier
.prototype._createModifier = function() {
292 AbstractToolbarModifier
.prototype._createModifier
.call( this );
294 this.modifyContainer
.setHtml( this._toolbarConfigToListString() );
296 var groupLi
= this.modifyContainer
.find( 'li[data-type="group"]' );
298 this.modifyContainer
.on( 'mouseleave', function() {
299 this._dehighlightActiveToolGroup();
302 var max
= groupLi
.count();
303 for ( var i
= 0; i
< max
; i
+= 1 ) {
304 groupLi
.getItem( i
).on( 'mouseenter', onGroupHover
);
307 function onGroupHover() {
308 that
._highlightGroup( this.data( 'name' ) );
311 CKEDITOR
.document
.on( 'keypress', function( e
) {
312 var nativeEvent
= e
.data
.$,
313 keyCode
= nativeEvent
.keyCode
,
314 spaceOrEnter
= ( keyCode
=== 32 || keyCode
=== 13 ),
315 active
= new CKEDITOR
.dom
.element( CKEDITOR
.document
.$.activeElement
);
317 var mainContainer
= active
.getAscendant( function( node
) {
318 return node
.$ === that
.mainContainer
.$;
321 if ( !mainContainer
|| !spaceOrEnter
) {
325 if ( active
.data( 'type' ) === 'button' ) {
326 active
.findOne( 'input' ).$.click();
330 this.modifyContainer
.on( 'click', function( e
) {
331 var origEvent
= e
.data
.$,
332 target
= new CKEDITOR
.dom
.element( ( origEvent
.target
|| origEvent
.srcElement
) ),
333 relativeGroupOrSeparatorLi
= ToolbarModifier
.getGroupOrSeparatorLiAncestor( target
);
335 if ( !relativeGroupOrSeparatorLi
) {
339 that
.cachedActiveElement
= document
.activeElement
;
342 if ( target
.$ instanceof HTMLInputElement
)
343 that
._handleCheckboxClicked( target
);
346 else if ( target
.$ instanceof HTMLButtonElement
) {
347 if ( origEvent
.preventDefault
)
348 origEvent
.preventDefault();
350 origEvent
.returnValue
= false;
352 var result
= that
._handleAnchorClicked( target
.$ );
354 if ( result
&& result
.action
== 'remove' )
359 var elementType
= relativeGroupOrSeparatorLi
.data( 'type' ),
360 elementName
= relativeGroupOrSeparatorLi
.data( 'name' );
362 that
._setActiveElement( elementType
, elementName
);
364 if ( that
.cachedActiveElement
)
365 that
.cachedActiveElement
.focus();
368 if ( !this.toolbarContainer
) {
369 this._createToolbar();
370 this.toolbarContainer
.insertBefore( this.mainContainer
.getChildren().getItem( 0 ) );
373 this.showToolbarBtnsByGroupName( 'edit' );
375 if ( !this.configContainer
) {
376 this.configContainer
= new CKEDITOR
.dom
.element( 'div' );
377 this.configContainer
.addClass( 'configContainer' );
378 this.configContainer
.addClass( 'hidden' );
380 this.mainContainer
.append( this.configContainer
);
383 return this.mainContainer
;
387 * Show toolbar buttons related to group name provided in argument
388 * and hide other buttons
389 * Please note: this method works on toolbar in tool, which is located
392 * @param {String} groupName
394 ToolbarModifier
.prototype.showToolbarBtnsByGroupName = function( groupName
) {
395 if ( !this.toolbarContainer
) {
399 var allButtons
= this.toolbarContainer
.find( 'button' );
401 var max
= allButtons
.count();
402 for ( var i
= 0; i
< max
; i
+= 1 ) {
403 var currentBtn
= allButtons
.getItem( i
);
405 if ( currentBtn
.data( 'group' ) == groupName
)
406 currentBtn
.removeClass( 'hidden' );
408 currentBtn
.addClass( 'hidden' );
414 * Parse group "model" to configuration value
416 * @param {Object} group
420 ToolbarModifier
.parseGroupToConfigValue = function( group
) {
421 if ( group
.type
== 'separator' ) {
425 var groups
= group
.groups
,
428 delete group
.totalBtns
;
429 for ( var i
= 0; i
< max
; i
+= 1 ) {
430 groups
[ i
] = groups
[ i
].name
;
437 * Find closest Li ancestor in DOM tree which is group or separator element
439 * @param {CKEDITOR.dom.element} element
440 * @returns {CKEDITOR.dom.element}
442 ToolbarModifier
.getGroupOrSeparatorLiAncestor = function( element
) {
443 if ( element
.$ instanceof HTMLLIElement
&& element
.data( 'type' ) == 'group' )
446 return ToolbarModifier
.getFirstAncestor( element
, function( ancestor
) {
447 var type
= ancestor
.data( 'type' );
449 return ( type
== 'group' || type
== 'separator' );
455 * Set active element in tool by provided type and name.
457 * @param {String} type
458 * @param {String} name
460 ToolbarModifier
.prototype._setActiveElement = function( type
, name
) {
461 // clear current active element
462 if ( this.currentActive
)
463 this.currentActive
.elem
.removeClass( 'active' );
465 if ( type
=== null ) {
466 this._dehighlightActiveToolGroup();
467 this.currentActive
= null;
471 var liElem
= this.mainContainer
.findOne( 'ul[data-type=table-body] li[data-type="' + type
+ '"][data-name="' + name
+ '"]' );
473 liElem
.addClass( 'active' );
476 this.currentActive
= {
482 // highlight group in toolbar
483 if ( type
== 'group' )
484 this._highlightGroup( name
);
486 if ( type
== 'separator' )
487 this._dehighlightActiveToolGroup();
491 * @returns {CKEDITOR.dom.element|null}
493 ToolbarModifier
.prototype.getActiveToolGroup = function() {
494 if ( this.editorInstance
.container
)
495 return this.editorInstance
.container
.findOne( '.cke_toolgroup.active, .cke_toolbar.active' );
503 ToolbarModifier
.prototype._dehighlightActiveToolGroup = function() {
504 var currentActive
= this.getActiveToolGroup();
507 currentActive
.removeClass( 'active' );
509 // @see ToolbarModifier.prototype._highlightGroup.
510 if ( this.editorInstance
.container
) {
511 this.editorInstance
.container
.removeClass( 'some-toolbar-active' );
516 * Highlight group by its name, and dehighlight current group.
518 * @param {String} name
520 ToolbarModifier
.prototype._highlightGroup = function( name
) {
521 if ( !this.editorInstance
.container
)
524 var foundBtnName
= this.getFirstEnabledButtonInGroup( name
),
525 foundBtn
= this.editorInstance
.container
.findOne( '.cke_button__' + foundBtnName
+ ', .cke_combo__' + foundBtnName
);
527 this._dehighlightActiveToolGroup();
529 // Helpful to dim other toolbar groups if one is highlighted.
530 if ( this.editorInstance
.container
) {
531 this.editorInstance
.container
.addClass( 'some-toolbar-active' );
535 var btnToolbar
= ToolbarModifier
.getFirstAncestor( foundBtn
, function( ancestor
) {
536 return ancestor
.hasClass( 'cke_toolbar' );
540 btnToolbar
.addClass( 'active' );
545 * @param {String} groupName
546 * @return {String|null}
548 ToolbarModifier
.prototype.getFirstEnabledButtonInGroup = function( groupName
) {
549 var groups
= this.actualConfig
.toolbarGroups
,
550 groupIndex
= this.getGroupIndex( groupName
),
551 group
= groups
[ groupIndex
];
553 if ( groupIndex
=== -1 ) {
557 var max
= group
.groups
? group
.groups
.length : 0;
558 for ( var i
= 0; i
< max
; i
+= 1 ) {
559 var currSubgroupName
= group
.groups
[ i
].name
,
560 firstEnabled
= this.getFirstEnabledButtonInSubgroup( currSubgroupName
);
569 * @param {String} subgroupName
570 * @returns {String|null}
572 ToolbarModifier
.prototype.getFirstEnabledButtonInSubgroup = function( subgroupName
) {
573 var subgroupBtns
= this.fullToolbarEditor
.buttonsByGroup
[ subgroupName
];
575 var max
= subgroupBtns
? subgroupBtns
.length : 0;
576 for ( var i
= 0; i
< max
; i
+= 1 ) {
577 var currBtnName
= subgroupBtns
[ i
].name
;
578 if ( !this.isButtonRemoved( currBtnName
) )
586 * Sets up parameters and call adequate action.
588 * @param {CKEDITOR.dom.element} checkbox
591 ToolbarModifier
.prototype._handleCheckboxClicked = function( checkbox
) {
592 var closestLi
= checkbox
.getAscendant( 'li' ),
593 elementName
= closestLi
.data( 'name' ),
594 aboutToAddToRemoved
= !checkbox
.$.checked
;
596 if ( aboutToAddToRemoved
)
597 this._addButtonToRemoved( elementName
);
599 this._removeButtonFromRemoved( elementName
);
603 * Sets up parameters and call adequate action.
605 * @param {HTMLAnchorElement} anchor
608 ToolbarModifier
.prototype._handleAnchorClicked = function( anchor
) {
609 var anchorDOM
= new CKEDITOR
.dom
.element( anchor
),
610 relativeLi
= anchorDOM
.getAscendant( 'li' ),
611 relativeUl
= relativeLi
.getAscendant( 'ul' ),
612 elementType
= relativeLi
.data( 'type' ),
613 elementName
= relativeLi
.data( 'name' ),
614 direction
= anchorDOM
.data( 'direction' ),
615 nearestLi
= ( direction
=== 'up' ? relativeLi
.getPrevious() : relativeLi
.getNext() ),
621 if ( anchorDOM
.hasClass( 'disabled' ) )
624 // remove separator and nothing else
625 if ( anchorDOM
.hasClass( 'remove' ) ) {
627 this._removeSeparator( relativeLi
.data( 'name' ) );
628 this._setActiveElement( null );
629 return { action: 'remove' };
632 if ( !anchorDOM
.hasClass( 'move' ) || !nearestLi
)
633 return { action: null };
635 // move group or separator
636 if ( elementType
=== 'group' || elementType
=== 'separator' ) {
637 groupName
= elementName
;
638 newIndex
= this._moveGroup( direction
, groupName
);
642 if ( elementType
=== 'subgroup' ) {
643 subgroupName
= elementName
;
644 groupName
= relativeLi
.getAscendant( 'li' ).data( 'name' );
645 newIndex
= this._moveSubgroup( direction
, groupName
, subgroupName
);
649 if ( direction
=== 'up' )
650 relativeLi
.insertBefore( relativeUl
.getChild( newIndex
) );
652 if ( direction
=== 'down' )
653 relativeLi
.insertAfter( relativeUl
.getChild( newIndex
) );
655 // Should know whether there is next li element after modifications.
656 var nextLi
= relativeLi
;
658 // We are looking for next li element in list (to check whether current one is the last one)
660 while ( nextLi
= ( direction
=== 'up' ? nextLi
.getPrevious() : nextLi
.getNext() ) ) {
661 if ( !this.emptyVisible
&& nextLi
.hasClass( 'empty' ) ) {
669 // If not found, it means that we reached end.
671 var selector
= ( '[data-direction="' + ( direction
=== 'up' ? 'down' : 'up' ) + '"]' );
673 // Shifting direction.
674 this.cachedActiveElement
= anchorDOM
.getParent().findOne( selector
);
677 this._refreshMoveBtnsAvalibility();
678 this._refreshBtnTabIndexes();
686 * First element can not be moved up, and last element can not be moved down,
687 * so they are disabled.
689 ToolbarModifier
.prototype._refreshMoveBtnsAvalibility = function() {
691 disabledBtns
= this.mainContainer
.find( 'ul[data-type=table-body] li > p > span > button.move.disabled' );
693 // enabling all disabled buttons
694 var max
= disabledBtns
.count();
695 for ( var i
= 0; i
< max
; i
+= 1 ) {
696 var currentBtn
= disabledBtns
.getItem( i
);
697 currentBtn
.removeClass( 'disabled' );
701 function disableElementsInLists( ulList
) {
702 var max
= ulList
.count();
703 for ( i
= 0; i
< max
; i
+= 1 ) {
704 that
._disableElementsInList( ulList
.getItem( i
) );
708 // Disable buttons in toolbars.
709 disableElementsInLists( this.mainContainer
.find( 'ul[data-type=table-body]' ) );
711 // Disable buttons in toolbar groups.
712 disableElementsInLists( this.mainContainer
.find( 'ul[data-type=table-body] > li > ul' ) );
718 ToolbarModifier
.prototype._refreshBtnTabIndexes = function() {
719 var tabindexed
= this.mainContainer
.find( '[data-tab="true"]' );
721 var max
= tabindexed
.count();
722 for ( var i
= 0; i
< max
; i
++ ) {
723 var item
= tabindexed
.getItem( i
),
724 disabled
= item
.hasClass( 'disabled' );
726 item
.setAttribute( 'tabindex', disabled
? -1 : i
);
731 * Disable buttons to move elements up and down which should be disabled.
733 * @param {CKEDITOR.dom.element} ul
736 ToolbarModifier
.prototype._disableElementsInList = function( ul
) {
737 var liList
= ul
.getChildren();
739 if ( !liList
.count() )
742 var firstDisabled
, lastDisabled
;
743 if ( this.emptyVisible
) {
744 firstDisabled
= ul
.getFirst();
745 lastDisabled
= ul
.getLast();
747 firstDisabled
= ul
.getFirst( isNotEmptyChecker
);
748 lastDisabled
= ul
.getLast( isNotEmptyChecker
);
751 function isNotEmptyChecker( element
) {
752 return !element
.hasClass( 'empty' );
756 var firstDisabledBtn
= firstDisabled
.findOne( 'p button[data-direction="up"]' );
759 var lastDisabledBtn
= lastDisabled
.findOne( 'p button[data-direction="down"]' );
761 if ( firstDisabledBtn
) {
762 firstDisabledBtn
.addClass( 'disabled' );
763 firstDisabledBtn
.setAttribute( 'tabindex', '-1' );
766 if ( lastDisabledBtn
) {
767 lastDisabledBtn
.addClass( 'disabled' );
768 lastDisabledBtn
.setAttribute( 'tabindex', '-1' );
773 * Gets group index in actual config toolbarGroups
775 * @param {String} name
778 ToolbarModifier
.prototype.getGroupIndex = function( name
) {
779 var groups
= this.actualConfig
.toolbarGroups
;
781 var max
= groups
.length
;
782 for ( var i
= 0; i
< max
; i
+= 1 ) {
783 if ( groups
[ i
].name
=== name
)
791 * Handle adding separator.
795 ToolbarModifier
.prototype._addSeparator = function() {
796 var separatorIndex
= this._determineSeparatorToAddIndex(),
797 separator
= ToolbarModifier
.createSeparatorLiteral(),
798 domSeparator
= CKEDITOR
.dom
.element
.createFromHtml( ToolbarModifier
.getToolbarSeparatorString( separator
) );
800 this.actualConfig
.toolbarGroups
.splice( separatorIndex
, 0, separator
);
802 domSeparator
.insertBefore( this.modifyContainer
.findOne( 'ul[data-type=table-body]' ).getChild( separatorIndex
) );
804 this._setActiveElement( 'separator', separator
.name
);
805 this._refreshMoveBtnsAvalibility();
806 this._refreshBtnTabIndexes();
807 this._refreshEditor();
811 * Handle removing separator.
813 * @param {String} name
815 ToolbarModifier
.prototype._removeSeparator = function( name
) {
816 var separatorIndex
= CKEDITOR
.tools
.indexOf( this.actualConfig
.toolbarGroups
, function( group
) {
817 return group
.type
== 'separator' && group
.name
== name
;
820 this.actualConfig
.toolbarGroups
.splice( separatorIndex
, 1 );
822 this._refreshMoveBtnsAvalibility();
823 this._refreshBtnTabIndexes();
824 this._refreshEditor();
828 * Determine index where separator should be added, based on currently selected element.
833 ToolbarModifier
.prototype._determineSeparatorToAddIndex = function() {
834 if ( !this.currentActive
)
838 if ( this.currentActive
.elem
.data( 'type' ) == 'group' || this.currentActive
.elem
.data( 'type' ) == 'separator' )
839 groupLi
= this.currentActive
.elem
;
841 groupLi
= this.currentActive
.elem
.getAscendant( 'li' );
843 return groupLi
.getIndex();
847 * @param {Array} elementsArray
848 * @param {Number} elementIndex
849 * @param {String} direction
853 ToolbarModifier
.prototype._moveElement = function( elementsArray
, elementIndex
, direction
) {
856 if ( this.emptyVisible
)
857 nextIndex
= ( direction
== 'down' ? elementIndex
+ 1 : elementIndex
- 1 );
859 // When empty elements are not visible, there is need to skip them.
860 nextIndex
= ToolbarModifier
.getFirstElementIndexWith( elementsArray
, elementIndex
, direction
, isEmptyOrSeparatorChecker
);
863 function isEmptyOrSeparatorChecker( element
) {
864 return element
.totalBtns
|| element
.type
== 'separator';
867 var offset
= nextIndex
- elementIndex
;
869 return ToolbarModifier
.moveTo( offset
, elementsArray
, elementIndex
);
873 * Moves group located in config level up or down and refresh editor.
875 * @param {String} direction
876 * @param {String} groupName
879 ToolbarModifier
.prototype._moveGroup = function( direction
, groupName
) {
880 var groupIndex
= this.getGroupIndex( groupName
),
881 groups
= this.actualConfig
.toolbarGroups
,
882 newIndex
= this._moveElement( groups
, groupIndex
, direction
);
884 this._refreshMoveBtnsAvalibility();
885 this._refreshBtnTabIndexes();
886 this._refreshEditor();
892 * Moves subgroup located in config level up or down and refresh editor.
894 * @param {String} direction
895 * @param {String} groupName
896 * @param {String} subgroupName
899 ToolbarModifier
.prototype._moveSubgroup = function( direction
, groupName
, subgroupName
) {
900 var groupIndex
= this.getGroupIndex( groupName
),
901 groups
= this.actualConfig
.toolbarGroups
,
902 group
= groups
[ groupIndex
],
903 subgroupIndex
= CKEDITOR
.tools
.indexOf( group
.groups
, function( subgroup
) {
904 return subgroup
.name
== subgroupName
;
906 newIndex
= this._moveElement( group
.groups
, subgroupIndex
, direction
);
908 this._refreshEditor();
914 * Set `totalBtns` property in `actualConfig.toolbarGroups` elements.
918 ToolbarModifier
.prototype._calculateTotalBtns = function() {
919 var groups
= this.actualConfig
.toolbarGroups
;
921 var i
= groups
.length
;
924 var currentGroup
= groups
[ i
],
925 totalBtns
= ToolbarModifier
.getTotalGroupButtonsNumber( currentGroup
, this.fullToolbarEditor
);
927 if ( currentGroup
.type
== 'separator' ) {
928 // nothing to do with separator
932 currentGroup
.totalBtns
= totalBtns
;
937 * Add button to removeButtons field in config and refresh editor.
939 * @param {String} buttonName
942 ToolbarModifier
.prototype._addButtonToRemoved = function( buttonName
) {
943 if ( CKEDITOR
.tools
.indexOf( this.removedButtons
, buttonName
) != -1 )
944 throw 'Button already added to removed';
946 this.removedButtons
.push( buttonName
);
947 this.actualConfig
.removeButtons
= this.removedButtons
.join( ',' );
948 this._refreshEditor();
952 * Remove button from removeButtons field in config and refresh editor.
954 * @param {String} buttonName
957 ToolbarModifier
.prototype._removeButtonFromRemoved = function( buttonName
) {
958 var foundAtIndex
= CKEDITOR
.tools
.indexOf( this.removedButtons
, buttonName
);
960 if ( foundAtIndex
=== -1 )
961 throw 'Trying to remove button from removed, but not found';
963 this.removedButtons
.splice( foundAtIndex
, 1 );
964 this.actualConfig
.removeButtons
= this.removedButtons
.join( ',' );
965 this._refreshEditor();
969 * Parse group "model" to configuration value
971 * @param {Object} group
975 ToolbarModifier
.parseGroupToConfigValue = function( group
) {
976 if ( group
.type
== 'separator' ) {
980 var groups
= group
.groups
,
983 delete group
.totalBtns
;
984 for ( var i
= 0; i
< max
; i
+= 1 ) {
985 groups
[ i
] = groups
[ i
].name
;
992 * Find closest Li ancestor in DOM tree which is group or separator element
994 * @param {CKEDITOR.dom.element} element
995 * @returns {CKEDITOR.dom.element}
998 ToolbarModifier
.getGroupOrSeparatorLiAncestor = function( element
) {
999 if ( element
.$ instanceof HTMLLIElement
&& element
.data( 'type' ) == 'group' )
1002 return ToolbarModifier
.getFirstAncestor( element
, function( ancestor
) {
1003 var type
= ancestor
.data( 'type' );
1005 return ( type
== 'group' || type
== 'separator' );
1011 * Create separator literal with unique id.
1017 ToolbarModifier
.createSeparatorLiteral = function() {
1020 name: ( 'separator' + CKEDITOR
.tools
.getNextNumber() )
1025 * Creates HTML unordered list string based on toolbarGroups field in config.
1030 ToolbarModifier
.prototype._toolbarConfigToListString = function() {
1031 var groups
= this.actualConfig
.toolbarGroups
|| [],
1032 listString
= '<ul data-type="table-body">';
1034 var max
= groups
.length
;
1035 for ( var i
= 0; i
< max
; i
+= 1 ) {
1036 var currentGroup
= groups
[ i
];
1038 if ( currentGroup
.type
=== 'separator' )
1039 listString
+= ToolbarModifier
.getToolbarSeparatorString( currentGroup
);
1041 listString
+= this._getToolbarGroupString( currentGroup
);
1044 listString
+= '</ul>';
1046 var headerString
= ToolbarModifier
.getToolbarHeaderString();
1048 return headerString
+ listString
;
1052 * Created HTML group list element based on group field in config.
1054 * @param {Object} group
1058 ToolbarModifier
.prototype._getToolbarGroupString = function( group
) {
1059 var subgroups
= group
.groups
,
1064 'data-type="group" ',
1065 'data-name="', group
.name
, '" ',
1066 ( group
.totalBtns
? '' : 'class="empty"' ),
1069 groupString
+= ToolbarModifier
.getToolbarElementPreString( group
) + '<ul>';
1071 var max
= subgroups
.length
;
1073 for ( var i
= 0; i
< max
; i
+= 1 ) {
1074 var currentSubgroup
= subgroups
[ i
],
1075 subgroupBtns
= this.fullToolbarEditor
.buttonsByGroup
[ currentSubgroup
.name
];
1077 groupString
+= this._getToolbarSubgroupString( currentSubgroup
, subgroupBtns
);
1079 groupString
+= '</ul></li>';
1085 * @param {Object} separator
1089 ToolbarModifier
.getToolbarSeparatorString = function( separator
) {
1092 'data-type="', separator
.type
, '" ',
1093 'data-name="', separator
.name
, '"',
1095 ToolbarModifier
.getToolbarElementPreString( 'row separator' ),
1103 ToolbarModifier
.getToolbarHeaderString = function() {
1104 return '<ul data-type="table-header">' +
1105 '<li data-type="header">' +
1109 '<p>Toolbar groups</p>' +
1110 '<p>Toolbar group items</p>' +
1118 * Find and return first ancestor of element provided in first argument
1119 * which match the criteria checked in function provided in second argument.
1121 * @param {CKEDITOR.dom.element} element
1122 * @param {Function} checker
1123 * @returns {CKEDITOR.dom.element|null}
1125 ToolbarModifier
.getFirstAncestor = function( element
, checker
) {
1126 var ancestors
= element
.getParents(),
1127 i
= ancestors
.length
;
1130 if ( checker( ancestors
[ i
] ) )
1131 return ancestors
[ i
];
1138 * Looking through array elements start from index provided in second argument
1139 * and go 'up' or 'down' in array
1140 * last argument is condition checker which should return Boolean value
1144 * ToolbarModifier.getFirstElementIndexWith( [3, 4, 8, 1, 4], 2, 'down', function( elem ) { return elem == 4; } ); // 4
1145 * ToolbarModifier.getFirstElementIndexWith( [3, 4, 8, 1, 4], 2, 'up', function( elem ) { return elem == 4; } ); // 1
1147 * @param {Array} array
1149 * @param {String} direction 'up' or 'down'
1150 * @param {Function} conditionChecker
1152 * @returns {Number} index of found element
1154 ToolbarModifier
.getFirstElementIndexWith = function( array
, i
, direction
, conditionChecker
) {
1155 function whileChecker() {
1157 if ( direction
=== 'up' )
1160 result
= ( ++i
< array
.length
);
1165 while ( whileChecker() ) {
1166 if ( conditionChecker( array
[ i
] ) )
1175 * Moves array element at index level up or down.
1178 * @param {String} direction
1179 * @param {Array} array
1180 * @param {Number} index
1183 ToolbarModifier
.moveTo = function( offset
, array
, index
) {
1184 var element
, newIndex
;
1187 element
= array
.splice( index
, 1 )[ 0 ];
1189 newIndex
= index
+ offset
;
1191 array
.splice( newIndex
, 0, element
);
1198 * @param {Object} subgroup
1201 ToolbarModifier
.getTotalSubGroupButtonsNumber = function( subgroup
, fullToolbarEditor
) {
1202 var subgroupName
= ( typeof subgroup
== 'string' ? subgroup : subgroup
.name
),
1203 subgroupBtns
= fullToolbarEditor
.buttonsByGroup
[ subgroupName
];
1205 return ( subgroupBtns
? subgroupBtns
.length : 0 );
1209 * Returns all buttons number in group which are nested in subgroups also.
1211 * @param {Object} group
1212 * @param {ToolbarModifier.FullToolbarEditor}
1216 ToolbarModifier
.getTotalGroupButtonsNumber = function( group
, fullToolbarEditor
) {
1218 subgroups
= group
.groups
;
1220 var max
= subgroups
? subgroups
.length : 0;
1221 for ( var i
= 0; i
< max
; i
+= 1 )
1222 total
+= ToolbarModifier
.getTotalSubGroupButtonsNumber( subgroups
[ i
], fullToolbarEditor
);
1228 * Creates HTML subgroup list element based on subgroup field in config.
1230 * @param {Object} subgroup
1231 * @param {Array} groupBtns
1235 ToolbarModifier
.prototype._getToolbarSubgroupString = function( subgroup
, groupBtns
) {
1236 var subgroupString
= '';
1240 'data-type="subgroup" ',
1241 'data-name="', subgroup
.name
, '" ',
1242 ( subgroup
.totalBtns
? '' : 'class="empty" ' ),
1245 subgroupString
+= ToolbarModifier
.getToolbarElementPreString( subgroup
.name
);
1246 subgroupString
+= '<ul>';
1248 var max
= groupBtns
? groupBtns
.length : 0;
1249 for ( var i
= 0; i
< max
; i
+= 1 )
1250 subgroupString
+= this.getButtonString( groupBtns
[ i
] );
1252 subgroupString
+= '</ul>';
1254 subgroupString
+= '</li>';
1256 return subgroupString
;
1260 * @param {String} buttonName
1261 * @returns {String|null}
1264 ToolbarModifier
.prototype._getConfigButtonName = function( buttonName
) {
1265 var items
= this.fullToolbarEditor
.editorInstance
.ui
.items
;
1268 for ( name
in items
) {
1269 if ( items
[ name
].name
== buttonName
)
1277 * @param {String} buttonName
1278 * @returns {Boolean}
1280 ToolbarModifier
.prototype.isButtonRemoved = function( buttonName
) {
1281 return CKEDITOR
.tools
.indexOf( this.removedButtons
, this._getConfigButtonName( buttonName
) ) != -1;
1285 * @param {CKEDITOR.ui.button/CKEDITOR.ui.richCombo} button
1289 ToolbarModifier
.prototype.getButtonString = function( button
) {
1290 var checked
= ( this.isButtonRemoved( button
.name
) ? '' : 'checked="checked"' );
1293 '<li data-tab="true" data-type="button" data-name="', this._getConfigButtonName( button
.name
), '">',
1294 '<label title="', button
.label
, '" >',
1300 button
.$.getOuterHtml(),
1307 * Creates group header string.
1309 * @param {Object|String} group
1313 ToolbarModifier
.getToolbarElementPreString = function( group
) {
1314 var name
= ( group
.name
? group
.name : group
);
1319 '<button title="Move element upward" data-tab="true" data-direction="up" class="move icon-up-big"></button>',
1320 '<button title="Move element downward" data-tab="true" data-direction="down" class="move icon-down-big"></button>',
1321 ( name
== 'row separator' ? '<button title="Remove element" data-tab="true" class="remove icon-trash"></button>' : '' ),
1330 * @param {String} cfg
1333 ToolbarModifier
.evaluateToolbarGroupsConfig = function( cfg
) {
1334 cfg
= ( function( cfg
) {
1335 var config
= {}, result
;
1339 result
= eval( '(' + cfg
+ ')' );
1342 result
= eval( cfg
);
1349 if ( config
.toolbarGroups
&& typeof config
.toolbarGroups
.length
=== 'number' ) {
1350 return JSON
.stringify( config
);
1351 } else if ( result
&& typeof result
.length
=== 'number' ) {
1352 return JSON
.stringify( { toolbarGroups: result
} );
1353 } else if ( result
&& result
.toolbarGroups
) {
1354 return JSON
.stringify( result
);
1364 return ToolbarModifier
;