2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
6 CKEDITOR
.plugins
.add( 'menu',
8 beforeInit : function( editor
)
10 var groups
= editor
.config
.menu_groups
.split( ',' ),
11 groupsOrder
= editor
._
.menuGroups
= {},
12 menuItems
= editor
._
.menuItems
= {};
14 for ( var i
= 0 ; i
< groups
.length
; i
++ )
15 groupsOrder
[ groups
[ i
] ] = i
+ 1;
18 * Registers an item group to the editor context menu in order to make it
19 * possible to associate it with menu items later.
20 * @name CKEDITOR.editor.prototype.addMenuGroup
21 * @param {String} name Specify a group name.
22 * @param {Number} [order=100] Define the display sequence of this group
23 * inside the menu. A smaller value gets displayed first.
25 editor
.addMenuGroup = function( name
, order
)
27 groupsOrder
[ name
] = order
|| 100;
31 * Adds an item from the specified definition to the editor context menu.
32 * @name CKEDITOR.editor.prototype.addMenuItem
33 * @param {String} name The menu item name.
34 * @param {CKEDITOR.menu.definition} definition The menu item definition.
36 editor
.addMenuItem = function( name
, definition
)
38 if ( groupsOrder
[ definition
.group
] )
39 menuItems
[ name
] = new CKEDITOR
.menuItem( this, name
, definition
);
43 * Adds one or more items from the specified definition array to the editor context menu.
44 * @name CKEDITOR.editor.prototype.addMenuItems
45 * @param {Array} definitions List of definitions for each menu item as if {@link CKEDITOR.editor.addMenuItem} is called.
47 editor
.addMenuItems = function( definitions
)
49 for ( var itemName
in definitions
)
51 this.addMenuItem( itemName
, definitions
[ itemName
] );
56 * Retrieves a particular menu item definition from the editor context menu.
57 * @name CKEDITOR.editor.prototype.getMenuItem
58 * @param {String} name The name of the desired menu item.
59 * @return {CKEDITOR.menu.definition}
61 editor
.getMenuItem = function( name
)
63 return menuItems
[ name
];
67 * Removes a particular menu item added before from the editor context menu.
68 * @name CKEDITOR.editor.prototype.removeMenuItem
69 * @param {String} name The name of the desired menu item.
72 editor
.removeMenuItem = function( name
)
74 delete menuItems
[ name
];
78 requires
: [ 'floatpanel' ]
83 CKEDITOR
.menu
= CKEDITOR
.tools
.createClass(
85 $ : function( editor
, definition
)
87 definition
= this._
.definition
= definition
|| {};
88 this.id
= CKEDITOR
.tools
.getNextId();
92 this._
.listeners
= [];
94 this._
.level
= definition
.level
|| 1;
96 var panelDefinition
= CKEDITOR
.tools
.extend( {}, definition
.panel
,
98 css
: editor
.skin
.editor
.css
,
99 level
: this._
.level
- 1,
103 var attrs
= panelDefinition
.block
.attributes
= ( panelDefinition
.attributes
|| {} );
104 // Provide default role of 'menu'.
105 !attrs
.role
&& ( attrs
.role
= 'menu' );
106 this._
.panelDefinition
= panelDefinition
;
113 var selection
= this.editor
.getSelection();
115 // Selection will be unavailable after menu shows up
116 // in IE, lock it now.
117 if ( CKEDITOR
.env
.ie
)
118 selection
&& selection
.lock();
120 var element
= selection
&& selection
.getStartElement(),
121 listeners
= this._
.listeners
,
125 // Call all listeners, filling the list of items to be displayed.
126 for ( var i
= 0 ; i
< listeners
.length
; i
++ )
128 var listenerItems
= listeners
[ i
]( element
, selection
);
132 for ( var itemName
in listenerItems
)
134 var item
= this.editor
.getMenuItem( itemName
);
136 if ( item
&& ( !item
.command
|| this.editor
.getCommand( item
.command
).state
) )
138 item
.state
= listenerItems
[ itemName
];
146 onClick : function( item
)
152 else if ( item
.command
)
153 this.editor
.execCommand( item
.command
);
156 onEscape : function( keystroke
)
158 var parent
= this.parent
;
159 // 1. If it's sub-menu, restore the last focused item
160 // of upper level menu.
161 // 2. In case of a top-menu, close it.
164 parent
._
.panel
.hideChild();
165 // Restore parent block item focus.
166 var parentBlock
= parent
._
.panel
._
.panel
._
.currentBlock
,
167 parentFocusIndex
= parentBlock
._
.focusIndex
;
168 parentBlock
._
.markItem( parentFocusIndex
);
170 else if ( keystroke
== 27 )
178 if ( CKEDITOR
.env
.ie
)
180 var selection
= this.editor
.getSelection();
181 selection
&& selection
.unlock();
184 this.onHide
&& this.onHide();
187 showSubMenu : function( index
)
189 var menu
= this._
.subMenu
,
190 item
= this.items
[ index
],
191 subItemDefs
= item
.getItems
&& item
.getItems();
193 // If this item has no subitems, we just hide the submenu, if
194 // available, and return back.
197 this._
.panel
.hideChild();
201 // Record parent menu focused item first (#3389).
202 var block
= this._
.panel
.getBlock( this.id
);
203 block
._
.focusIndex
= index
;
205 // Create the submenu, if not available, or clean the existing
211 menu
= this._
.subMenu
= new CKEDITOR
.menu( this.editor
,
212 CKEDITOR
.tools
.extend( {}, this._
.definition
, { level
: this._
.level
+ 1 }, true ) );
214 menu
._
.onClick
= CKEDITOR
.tools
.bind( this._
.onClick
, this );
217 // Add all submenu items to the menu.
218 for ( var subItemName
in subItemDefs
)
220 var subItem
= this.editor
.getMenuItem( subItemName
);
223 subItem
.state
= subItemDefs
[ subItemName
];
228 // Get the element representing the current item.
229 var element
= this._
.panel
.getBlock( this.id
).element
.getDocument().getById( this.id
+ String( index
) );
232 menu
.show( element
, 2 );
238 add : function( item
)
240 // Later we may sort the items, but Array#sort is not stable in
241 // some browsers, here we're forcing the original sequence with
242 // 'order' attribute if it hasn't been assigned. (#3868)
244 item
.order
= this.items
.length
;
246 this.items
.push( item
);
249 removeAll : function()
254 show : function( offsetParent
, corner
, offsetX
, offsetY
)
260 // Don't menu with zero items.
261 if ( ! this.items
.length
)
265 corner
= corner
|| ( this.editor
.lang
.dir
== 'rtl' ? 2 : 1 );
267 var items
= this.items
,
268 editor
= this.editor
,
269 panel
= this._
.panel
,
270 element
= this._
.element
;
272 // Create the floating panel for this menu.
275 panel
= this._
.panel
= new CKEDITOR
.ui
.floatPanel( this.editor
,
276 CKEDITOR
.document
.getBody(),
277 this._
.panelDefinition
,
280 panel
.onEscape
= CKEDITOR
.tools
.bind( function( keystroke
)
282 if ( this._
.onEscape( keystroke
) === false )
287 panel
.onHide
= CKEDITOR
.tools
.bind( function()
289 this._
.onHide
&& this._
.onHide();
293 // Create an autosize block inside the panel.
294 var block
= panel
.addBlock( this.id
, this._
.panelDefinition
.block
);
295 block
.autoSize
= true;
297 var keys
= block
.keys
;
298 keys
[ 40 ] = 'next'; // ARROW-DOWN
299 keys
[ 9 ] = 'next'; // TAB
300 keys
[ 38 ] = 'prev'; // ARROW-UP
301 keys
[ CKEDITOR
.SHIFT
+ 9 ] = 'prev'; // SHIFT + TAB
302 keys
[ ( editor
.lang
.dir
== 'rtl' ? 37 : 39 ) ]= CKEDITOR
.env
.ie
? 'mouseup' : 'click'; // ARROW-RIGHT/ARROW-LEFT(rtl)
303 keys
[ 32 ] = CKEDITOR
.env
.ie
? 'mouseup' : 'click'; // SPACE
304 CKEDITOR
.env
.ie
&& ( keys
[ 13 ] = 'mouseup' ); // Manage ENTER, since onclick is blocked in IE (#8041).
306 element
= this._
.element
= block
.element
;
307 element
.addClass( editor
.skinClass
);
309 var elementDoc
= element
.getDocument();
310 elementDoc
.getBody().setStyle( 'overflow', 'hidden' );
311 elementDoc
.getElementsByTag( 'html' ).getItem( 0 ).setStyle( 'overflow', 'hidden' );
313 this._
.itemOverFn
= CKEDITOR
.tools
.addFunction( function( index
)
315 clearTimeout( this._
.showSubTimeout
);
316 this._
.showSubTimeout
= CKEDITOR
.tools
.setTimeout( this._
.showSubMenu
, editor
.config
.menu_subMenuDelay
|| 400, this, [ index
] );
320 this._
.itemOutFn
= CKEDITOR
.tools
.addFunction( function( index
)
322 clearTimeout( this._
.showSubTimeout
);
326 this._
.itemClickFn
= CKEDITOR
.tools
.addFunction( function( index
)
328 var item
= this.items
[ index
];
330 if ( item
.state
== CKEDITOR
.TRISTATE_DISABLED
)
337 this._
.showSubMenu( index
);
339 this._
.onClick( item
);
344 // Put the items in the right order.
347 var chromeRoot
= editor
.container
.getChild( 1 ),
348 mixedContentClass
= chromeRoot
.hasClass( 'cke_mixed_dir_content' ) ? ' cke_mixed_dir_content' : '';
350 // Build the HTML that composes the menu and its items.
351 var output
= [ '<div class="cke_menu' + mixedContentClass
+ '" role="presentation">' ];
353 var length
= items
.length
,
354 lastGroup
= length
&& items
[ 0 ].group
;
356 for ( var i
= 0 ; i
< length
; i
++ )
358 var item
= items
[ i
];
359 if ( lastGroup
!= item
.group
)
361 output
.push( '<div class="cke_menuseparator" role="separator"></div>' );
362 lastGroup
= item
.group
;
365 item
.render( this, i
, output
);
368 output
.push( '</div>' );
370 // Inject the HTML inside the panel.
371 element
.setHtml( output
.join( '' ) );
373 CKEDITOR
.ui
.fire( 'ready', this );
377 this.parent
._
.panel
.showAsChild( panel
, this.id
, offsetParent
, corner
, offsetX
, offsetY
);
379 panel
.showBlock( this.id
, offsetParent
, corner
, offsetX
, offsetY
);
381 editor
.fire( 'menuShow', [ panel
] );
384 addListener : function( listenerFn
)
386 this._
.listeners
.push( listenerFn
);
389 hide : function( returnFocus
)
391 this._
.onHide
&& this._
.onHide();
392 this._
.panel
&& this._
.panel
.hide( returnFocus
);
397 function sortItems( items
)
399 items
.sort( function( itemA
, itemB
)
401 if ( itemA
.group
< itemB
.group
)
403 else if ( itemA
.group
> itemB
.group
)
406 return itemA
.order
< itemB
.order
? -1 :
407 itemA
.order
> itemB
.order
? 1 :
411 CKEDITOR
.menuItem
= CKEDITOR
.tools
.createClass(
413 $ : function( editor
, name
, definition
)
415 CKEDITOR
.tools
.extend( this, definition
,
419 className
: 'cke_button_' + name
422 // Transform the group name into its order number.
423 this.group
= editor
._
.menuGroups
[ this.group
];
425 this.editor
= editor
;
431 render : function( menu
, index
, output
)
433 var id
= menu
.id
+ String( index
),
434 state
= ( typeof this.state
== 'undefined' ) ? CKEDITOR
.TRISTATE_OFF
: this.state
;
436 var classes
= ' cke_' + (
437 state
== CKEDITOR
.TRISTATE_ON
? 'on' :
438 state
== CKEDITOR
.TRISTATE_DISABLED
? 'disabled' :
441 var htmlLabel
= this.label
;
443 if ( this.className
)
444 classes
+= ' ' + this.className
;
446 var hasSubMenu
= this.getItems
;
449 '<span class="cke_menuitem' + ( this.icon
&& this.icon
.indexOf( '.png' ) == -1 ? ' cke_noalphafix' : '' ) + '">' +
451 ' class="', classes
, '" href="javascript:void(\'', ( this.label
|| '' ).replace( "'", '' ), '\')"' +
452 ' title="', this.label
, '"' +
455 ' hidefocus="true"' +
457 ( hasSubMenu
? 'aria-haspopup="true"' : '' ) +
458 ( state
== CKEDITOR
.TRISTATE_DISABLED
? 'aria-disabled="true"' : '' ) +
459 ( state
== CKEDITOR
.TRISTATE_ON
? 'aria-pressed="true"' : '' ) );
461 // Some browsers don't cancel key events in the keydown but in the
463 // TODO: Check if really needed for Gecko+Mac.
464 if ( CKEDITOR
.env
.opera
|| ( CKEDITOR
.env
.gecko
&& CKEDITOR
.env
.mac
) )
467 ' onkeypress="return false;"' );
470 // With Firefox, we need to force the button to redraw, otherwise it
471 // will remain in the focus state.
472 if ( CKEDITOR
.env
.gecko
)
475 ' onblur="this.style.cssText = this.style.cssText;"' );
478 var offset
= ( this.iconOffset
|| 0 ) * -16;
480 // ' onkeydown="return CKEDITOR.ui.button._.keydown(', index, ', event);"' +
481 ' onmouseover="CKEDITOR.tools.callFunction(', menu
._
.itemOverFn
, ',', index
, ');"' +
482 ' onmouseout="CKEDITOR.tools.callFunction(', menu
._
.itemOutFn
, ',', index
, ');" ' +
483 ( CKEDITOR
.env
.ie
? 'onclick="return false;" onmouseup' : 'onclick' ) + // #188
484 '="CKEDITOR.tools.callFunction(', menu
._
.itemClickFn
, ',', index
, '); return false;"' +
486 '<span class="cke_icon_wrapper"><span class="cke_icon"' +
487 ( this.icon
? ' style="background-image:url(' + CKEDITOR
.getUrl( this.icon
) + ');background-position:0 ' + offset
+ 'px;"'
490 '<span class="cke_label">' );
495 '<span class="cke_menuarrow">',
497 ( this.editor
.lang
.dir
== 'rtl' ?
498 '9668' : // BLACK LEFT-POINTING POINTER
499 '9658' ), // BLACK RIGHT-POINTING POINTER
517 * The amount of time, in milliseconds, the editor waits before displaying submenu
518 * options when moving the mouse over options that contain submenus, like the
519 * "Cell Properties" entry for tables.
523 * // Remove the submenu delay.
524 * config.menu_subMenuDelay = 0;
528 * A comma separated list of items group names to be displayed in the context
529 * menu. The order of items will reflect the order specified in this list if
530 * no priority was defined in the groups.
532 * @default 'clipboard,form,tablecell,tablecellproperties,tablerow,tablecolumn,table,anchor,link,image,flash,checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea'
534 * config.menu_groups = 'clipboard,table,anchor,link,image';
536 CKEDITOR
.config
.menu_groups
=
539 'tablecell,tablecellproperties,tablerow,tablecolumn,table,'+
540 'anchor,link,image,flash,' +
541 'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div';