1 /* 2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. 3 For licensing, see LICENSE.html or http://ckeditor.com/license 4 */ 5 6 CKEDITOR.plugins.add( 'richcombo', 7 { 8 requires : [ 'floatpanel', 'listblock', 'button' ], 9 10 beforeInit : function( editor ) 11 { 12 editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler ); 13 } 14 }); 15 16 /** 17 * Button UI element. 18 * @constant 19 * @example 20 */ 21 CKEDITOR.UI_RICHCOMBO = 3; 22 23 CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass( 24 { 25 $ : function( definition ) 26 { 27 // Copy all definition properties to this object. 28 CKEDITOR.tools.extend( this, definition, 29 // Set defaults. 30 { 31 title : definition.label, 32 modes : { wysiwyg : 1 } 33 }); 34 35 // We don't want the panel definition in this object. 36 var panelDefinition = this.panel || {}; 37 delete this.panel; 38 39 this.id = CKEDITOR.tools.getNextNumber(); 40 41 this.document = ( panelDefinition 42 && panelDefinition.parent 43 && panelDefinition.parent.getDocument() ) 44 || CKEDITOR.document; 45 46 panelDefinition.className = ( panelDefinition.className || '' ) + ' cke_rcombopanel'; 47 48 this._ = 49 { 50 panelDefinition : panelDefinition, 51 items : {}, 52 state : CKEDITOR.TRISTATE_OFF 53 }; 54 }, 55 56 statics : 57 { 58 handler : 59 { 60 create : function( definition ) 61 { 62 return new CKEDITOR.ui.richCombo( definition ); 63 } 64 } 65 }, 66 67 proto : 68 { 69 renderHtml : function( editor ) 70 { 71 var output = []; 72 this.render( editor, output ); 73 return output.join( '' ); 74 }, 75 76 /** 77 * Renders the combo. 78 * @param {CKEDITOR.editor} editor The editor instance which this button is 79 * to be used by. 80 * @param {Array} output The output array to which append the HTML relative 81 * to this button. 82 * @example 83 */ 84 render : function( editor, output ) 85 { 86 var id = 'cke_' + this.id; 87 var clickFn = CKEDITOR.tools.addFunction( function( $element ) 88 { 89 var _ = this._; 90 91 if ( _.state == CKEDITOR.TRISTATE_DISABLED ) 92 return; 93 94 this.createPanel( editor ); 95 96 if ( _.on ) 97 { 98 _.panel.hide(); 99 return; 100 } 101 102 if ( !_.committed ) 103 { 104 _.list.commit(); 105 _.committed = 1; 106 } 107 108 var value = this.getValue(); 109 if ( value ) 110 _.list.mark( value ); 111 else 112 _.list.unmarkAll(); 113 114 _.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 ); 115 }, 116 this ); 117 118 var instance = { 119 id : id, 120 combo : this, 121 focus : function() 122 { 123 var element = CKEDITOR.document.getById( id ).getChild( 1 ); 124 element.focus(); 125 }, 126 execute : clickFn 127 }; 128 129 editor.on( 'mode', function() 130 { 131 this.setState( this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); 132 }, 133 this ); 134 135 var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) 136 { 137 ev = new CKEDITOR.dom.event( ev ); 138 139 var keystroke = ev.getKeystroke(); 140 switch ( keystroke ) 141 { 142 case 13 : // ENTER 143 case 32 : // SPACE 144 case 40 : // ARROW-DOWN 145 // Show panel 146 CKEDITOR.tools.callFunction( clickFn, element ); 147 break; 148 default : 149 // Delegate the default behavior to toolbar button key handling. 150 instance.onkey( instance, keystroke ); 151 } 152 153 // Avoid subsequent focus grab on editor document. 154 ev.preventDefault(); 155 }); 156 157 output.push( 158 '<span class="cke_rcombo">', 159 '<span id=', id ); 160 161 if ( this.className ) 162 output.push( ' class="', this.className, ' cke_off"'); 163 164 output.push( 165 '>' + 166 '<span class=cke_label>', this.label, '</span>' + 167 '<a hidefocus=true title="', this.title, '" tabindex="-1" href="javascript:void(\'', this.label, '\')"' ); 168 169 // Some browsers don't cancel key events in the keydown but in the 170 // keypress. 171 // TODO: Check if really needed for Gecko+Mac. 172 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) 173 { 174 output.push( 175 ' onkeypress="return false;"' ); 176 } 177 178 // With Firefox, we need to force it to redraw, otherwise it 179 // will remain in the focus state. 180 if ( CKEDITOR.env.gecko ) 181 { 182 output.push( 183 ' onblur="this.style.cssText = this.style.cssText;"' ); 184 } 185 186 output.push( 187 ' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' + 188 ' onclick="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' + 189 '<span>' + 190 '<span class="cke_accessibility">' + ( this.voiceLabel ? this.voiceLabel + ' ' : '' ) + '</span>' + 191 '<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' + 192 '</span>' + 193 '<span class=cke_openbutton></span>' + 194 '</a>' + 195 '</span>' + 196 '</span>' ); 197 198 if ( this.onRender ) 199 this.onRender(); 200 201 return instance; 202 }, 203 204 createPanel : function( editor ) 205 { 206 if ( this._.panel ) 207 return; 208 209 var panelDefinition = this._.panelDefinition, 210 panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), 211 panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), 212 list = panel.addListBlock( this.id, this.multiSelect ), 213 me = this; 214 215 panel.onShow = function() 216 { 217 if ( me.className ) 218 this.element.getFirst().addClass( me.className + '_panel' ); 219 220 me.setState( CKEDITOR.TRISTATE_ON ); 221 222 list.focus( !me.multiSelect && me.getValue() ); 223 224 me._.on = 1; 225 226 if ( me.onOpen ) 227 me.onOpen(); 228 }; 229 230 panel.onHide = function() 231 { 232 if ( me.className ) 233 this.element.getFirst().removeClass( me.className + '_panel' ); 234 235 me.setState( CKEDITOR.TRISTATE_OFF ); 236 237 me._.on = 0; 238 239 if ( me.onClose ) 240 me.onClose(); 241 }; 242 243 panel.onEscape = function() 244 { 245 panel.hide(); 246 me.document.getById( 'cke_' + me.id ).getFirst().getNext().focus(); 247 }; 248 249 list.onClick = function( value, marked ) 250 { 251 // Move the focus to the main windows, otherwise it will stay 252 // into the floating panel, even if invisible, and Safari and 253 // Opera will go a bit crazy. 254 me.document.getWindow().focus(); 255 256 if ( me.onClick ) 257 me.onClick.call( me, value, marked ); 258 259 if ( marked ) 260 me.setValue( value, me._.items[ value ] ); 261 else 262 me.setValue( '' ); 263 264 panel.hide(); 265 }; 266 267 this._.panel = panel; 268 this._.list = list; 269 270 panel.getBlock( this.id ).onHide = function() 271 { 272 me._.on = 0; 273 me.setState( CKEDITOR.TRISTATE_OFF ); 274 }; 275 276 if ( this.init ) 277 this.init(); 278 }, 279 280 setValue : function( value, text ) 281 { 282 this._.value = value; 283 284 var textElement = this.document.getById( 'cke_' + this.id + '_text' ); 285 286 if ( !value ) 287 { 288 text = this.label; 289 textElement.addClass( 'cke_inline_label' ); 290 } 291 else 292 textElement.removeClass( 'cke_inline_label' ); 293 textElement.setHtml( typeof text != 'undefined' ? text : value ); 294 }, 295 296 getValue : function() 297 { 298 return this._.value || ''; 299 }, 300 301 unmarkAll : function() 302 { 303 this._.list.unmarkAll(); 304 }, 305 306 mark : function( value ) 307 { 308 this._.list.mark( value ); 309 }, 310 311 hideItem : function( value ) 312 { 313 this._.list.hideItem( value ); 314 }, 315 316 hideGroup : function( groupTitle ) 317 { 318 this._.list.hideGroup( groupTitle ); 319 }, 320 321 showAll : function() 322 { 323 this._.list.showAll(); 324 }, 325 326 add : function( value, html, text ) 327 { 328 this._.items[ value ] = text || value; 329 this._.list.add( value, html, text ); 330 }, 331 332 startGroup : function( title ) 333 { 334 this._.list.startGroup( title ); 335 }, 336 337 commit : function() 338 { 339 this._.list.commit(); 340 }, 341 342 setState : function( state ) 343 { 344 if ( this._.state == state ) 345 return; 346 347 this.document.getById( 'cke_' + this.id ).setState( state ); 348 349 this._.state = state; 350 } 351 } 352 }); 353 354 CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) 355 { 356 this.add( name, CKEDITOR.UI_RICHCOMBO, definition ); 357 }; 358