/** * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'richcombo', { requires: 'floatpanel,listblock,button', beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler ); } } ); ( function() { var template = '' + '{label}' + '' + '{label}' + '' + '' + // BLACK DOWN-POINTING TRIANGLE ( CKEDITOR.env.hc ? '▼' : CKEDITOR.env.air ? ' ' : '' ) + '' + '' + '' + ''; var rcomboTpl = CKEDITOR.addTemplate( 'combo', template ); /** * Button UI element. * * @readonly * @property {String} [='richcombo'] * @member CKEDITOR */ CKEDITOR.UI_RICHCOMBO = 'richcombo'; /** * @class * @todo */ CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass( { $: function( definition ) { // Copy all definition properties to this object. CKEDITOR.tools.extend( this, definition, // Set defaults. { // The combo won't participate in toolbar grouping. canGroup: false, title: definition.label, modes: { wysiwyg: 1 }, editorFocus: 1 } ); // We don't want the panel definition in this object. var panelDefinition = this.panel || {}; delete this.panel; this.id = CKEDITOR.tools.getNextNumber(); this.document = ( panelDefinition.parent && panelDefinition.parent.getDocument() ) || CKEDITOR.document; panelDefinition.className = 'cke_combopanel'; panelDefinition.block = { multiSelect: panelDefinition.multiSelect, attributes: panelDefinition.attributes }; panelDefinition.toolbarRelated = true; this._ = { panelDefinition: panelDefinition, items: {} }; }, proto: { renderHtml: function( editor ) { var output = []; this.render( editor, output ); return output.join( '' ); }, /** * Renders the combo. * * @param {CKEDITOR.editor} editor The editor instance which this button is * to be used by. * @param {Array} output The output array to which append the HTML relative * to this button. */ render: function( editor, output ) { var env = CKEDITOR.env; var id = 'cke_' + this.id; var clickFn = CKEDITOR.tools.addFunction( function( el ) { // Restore locked selection in Opera. if ( selLocked ) { editor.unlockSelection( 1 ); selLocked = 0; } instance.execute( el ); }, this ); var combo = this; var instance = { id: id, combo: this, focus: function() { var element = CKEDITOR.document.getById( id ).getChild( 1 ); element.focus(); }, execute: function( el ) { var _ = combo._; if ( _.state == CKEDITOR.TRISTATE_DISABLED ) return; combo.createPanel( editor ); if ( _.on ) { _.panel.hide(); return; } combo.commit(); var value = combo.getValue(); if ( value ) _.list.mark( value ); else _.list.unmarkAll(); _.panel.showBlock( combo.id, new CKEDITOR.dom.element( el ), 4 ); }, clickFn: clickFn }; function updateState() { // Don't change state while richcombo is active (http://dev.ckeditor.com/ticket/11793). if ( this.getState() == CKEDITOR.TRISTATE_ON ) return; var state = this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; if ( editor.readOnly && !this.readOnly ) state = CKEDITOR.TRISTATE_DISABLED; this.setState( state ); this.setValue( '' ); // Let plugin to disable button. if ( state != CKEDITOR.TRISTATE_DISABLED && this.refresh ) this.refresh(); } // Update status when activeFilter, mode, selection or readOnly changes. editor.on( 'activeFilterChange', updateState, this ); editor.on( 'mode', updateState, this ); editor.on( 'selectionChange', updateState, this ); // If this combo is sensitive to readOnly state, update it accordingly. !this.readOnly && editor.on( 'readOnly', updateState, this ); var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) { ev = new CKEDITOR.dom.event( ev ); var keystroke = ev.getKeystroke(); switch ( keystroke ) { case 13: // ENTER case 32: // SPACE case 40: // ARROW-DOWN // Show panel CKEDITOR.tools.callFunction( clickFn, element ); break; default: // Delegate the default behavior to toolbar button key handling. instance.onkey( instance, keystroke ); } // Avoid subsequent focus grab on editor document. ev.preventDefault(); } ); var focusFn = CKEDITOR.tools.addFunction( function() { instance.onfocus && instance.onfocus(); } ); var selLocked = 0; // For clean up instance.keyDownFn = keyDownFn; var params = { id: id, name: this.name || this.command, label: this.label, title: this.title, cls: this.className || '', titleJs: env.gecko && !env.hc ? '' : ( this.title || '' ).replace( "'", '' ), keydownFn: keyDownFn, focusFn: focusFn, clickFn: clickFn }; rcomboTpl.output( params, output ); if ( this.onRender ) this.onRender(); return instance; }, createPanel: function( editor ) { if ( this._.panel ) return; var panelDefinition = this._.panelDefinition, panelBlockDefinition = this._.panelDefinition.block, panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), namedPanelCls = 'cke_combopanel__' + this.name, panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), list = panel.addListBlock( this.id, panelBlockDefinition ), me = this; panel.onShow = function() { this.element.addClass( namedPanelCls ); me.setState( CKEDITOR.TRISTATE_ON ); me._.on = 1; me.editorFocus && !editor.focusManager.hasFocus && editor.focus(); if ( me.onOpen ) me.onOpen(); }; panel.onHide = function( preventOnClose ) { this.element.removeClass( namedPanelCls ); me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); me._.on = 0; if ( !preventOnClose && me.onClose ) me.onClose(); }; panel.onEscape = function() { // Hide drop-down with focus returned. panel.hide( 1 ); }; list.onClick = function( value, marked ) { if ( me.onClick ) me.onClick.call( me, value, marked ); panel.hide(); }; this._.panel = panel; this._.list = list; panel.getBlock( this.id ).onHide = function() { me._.on = 0; me.setState( CKEDITOR.TRISTATE_OFF ); }; if ( this.init ) this.init(); }, setValue: function( value, text ) { this._.value = value; var textElement = this.document.getById( 'cke_' + this.id + '_text' ); if ( textElement ) { if ( !( value || text ) ) { text = this.label; textElement.addClass( 'cke_combo_inlinelabel' ); } else { textElement.removeClass( 'cke_combo_inlinelabel' ); } textElement.setText( typeof text != 'undefined' ? text : value ); } }, getValue: function() { return this._.value || ''; }, unmarkAll: function() { this._.list.unmarkAll(); }, mark: function( value ) { this._.list.mark( value ); }, hideItem: function( value ) { this._.list.hideItem( value ); }, hideGroup: function( groupTitle ) { this._.list.hideGroup( groupTitle ); }, showAll: function() { this._.list.showAll(); }, add: function( value, html, text ) { this._.items[ value ] = text || value; this._.list.add( value, html, text ); }, startGroup: function( title ) { this._.list.startGroup( title ); }, commit: function() { if ( !this._.committed ) { this._.list.commit(); this._.committed = 1; CKEDITOR.ui.fire( 'ready', this ); } this._.committed = 1; }, setState: function( state ) { if ( this._.state == state ) return; var el = this.document.getById( 'cke_' + this.id ); el.setState( state, 'cke_combo' ); state == CKEDITOR.TRISTATE_DISABLED ? el.setAttribute( 'aria-disabled', true ) : el.removeAttribute( 'aria-disabled' ); this._.state = state; }, getState: function() { return this._.state; }, enable: function() { if ( this._.state == CKEDITOR.TRISTATE_DISABLED ) this.setState( this._.lastState ); }, disable: function() { if ( this._.state != CKEDITOR.TRISTATE_DISABLED ) { this._.lastState = this._.state; this.setState( CKEDITOR.TRISTATE_DISABLED ); } } }, /** * Represents richCombo handler object. * * @class CKEDITOR.ui.richCombo.handler * @singleton * @extends CKEDITOR.ui.handlerDefinition */ statics: { handler: { /** * Transforms a richCombo definition in a {@link CKEDITOR.ui.richCombo} instance. * * @param {Object} definition * @returns {CKEDITOR.ui.richCombo} */ create: function( definition ) { return new CKEDITOR.ui.richCombo( definition ); } } } } ); /** * @param {String} name * @param {Object} definition * @member CKEDITOR.ui * @todo */ CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) { this.add( name, CKEDITOR.UI_RICHCOMBO, definition ); }; } )();