1 /* 2 Copyright (c) 2003-2012, 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 = 'richcombo'; 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 panelDefinition.block = 48 { 49 multiSelect : panelDefinition.multiSelect, 50 attributes : panelDefinition.attributes 51 }; 52 53 this._ = 54 { 55 panelDefinition : panelDefinition, 56 items : {}, 57 state : CKEDITOR.TRISTATE_OFF 58 }; 59 }, 60 61 statics : 62 { 63 handler : 64 { 65 create : function( definition ) 66 { 67 return new CKEDITOR.ui.richCombo( definition ); 68 } 69 } 70 }, 71 72 proto : 73 { 74 renderHtml : function( editor ) 75 { 76 var output = []; 77 this.render( editor, output ); 78 return output.join( '' ); 79 }, 80 81 /** 82 * Renders the combo. 83 * @param {CKEDITOR.editor} editor The editor instance which this button is 84 * to be used by. 85 * @param {Array} output The output array to which append the HTML relative 86 * to this button. 87 * @example 88 */ 89 render : function( editor, output ) 90 { 91 var env = CKEDITOR.env; 92 93 var id = 'cke_' + this.id; 94 var clickFn = CKEDITOR.tools.addFunction( function( $element ) 95 { 96 var _ = this._; 97 98 if ( _.state == CKEDITOR.TRISTATE_DISABLED ) 99 return; 100 101 this.createPanel( editor ); 102 103 if ( _.on ) 104 { 105 _.panel.hide(); 106 return; 107 } 108 109 this.commit(); 110 var value = this.getValue(); 111 if ( value ) 112 _.list.mark( value ); 113 else 114 _.list.unmarkAll(); 115 116 _.panel.showBlock( this.id, new CKEDITOR.dom.element( $element ), 4 ); 117 }, 118 this ); 119 120 var instance = { 121 id : id, 122 combo : this, 123 focus : function() 124 { 125 var element = CKEDITOR.document.getById( id ).getChild( 1 ); 126 element.focus(); 127 }, 128 clickFn : clickFn 129 }; 130 131 function updateState() 132 { 133 var state = this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; 134 this.setState( editor.readOnly && !this.readOnly ? CKEDITOR.TRISTATE_DISABLED : state ); 135 this.setValue( '' ); 136 } 137 138 editor.on( 'mode', updateState, this ); 139 // If this combo is sensitive to readOnly state, update it accordingly. 140 !this.readOnly && editor.on( 'readOnly', updateState, this); 141 142 var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) 143 { 144 ev = new CKEDITOR.dom.event( ev ); 145 146 var keystroke = ev.getKeystroke(); 147 switch ( keystroke ) 148 { 149 case 13 : // ENTER 150 case 32 : // SPACE 151 case 40 : // ARROW-DOWN 152 // Show panel 153 CKEDITOR.tools.callFunction( clickFn, element ); 154 break; 155 default : 156 // Delegate the default behavior to toolbar button key handling. 157 instance.onkey( instance, keystroke ); 158 } 159 160 // Avoid subsequent focus grab on editor document. 161 ev.preventDefault(); 162 }); 163 164 var focusFn = CKEDITOR.tools.addFunction( function() { instance.onfocus && instance.onfocus(); } ); 165 166 // For clean up 167 instance.keyDownFn = keyDownFn; 168 169 output.push( 170 '<span class="cke_rcombo" role="presentation">', 171 '<span id=', id ); 172 173 if ( this.className ) 174 output.push( ' class="', this.className, ' cke_off"'); 175 176 output.push( 177 ' role="presentation">', 178 '<span id="' + id+ '_label" class=cke_label>', this.label, '</span>', 179 '<a hidefocus=true title="', this.title, '" tabindex="-1"', 180 env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(\'' + this.label + '\')"', 181 ' role="button" aria-labelledby="', id , '_label" aria-describedby="', id, '_text" aria-haspopup="true"' ); 182 183 // Some browsers don't cancel key events in the keydown but in the 184 // keypress. 185 // TODO: Check if really needed for Gecko+Mac. 186 if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) 187 { 188 output.push( 189 ' onkeypress="return false;"' ); 190 } 191 192 // With Firefox, we need to force it to redraw, otherwise it 193 // will remain in the focus state. 194 if ( CKEDITOR.env.gecko ) 195 { 196 output.push( 197 ' onblur="this.style.cssText = this.style.cssText;"' ); 198 } 199 200 output.push( 201 ' onkeydown="CKEDITOR.tools.callFunction( ', keyDownFn, ', event, this );"' + 202 ' onfocus="return CKEDITOR.tools.callFunction(', focusFn, ', event);" ' + 203 ( CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick' ) + // #188 204 '="CKEDITOR.tools.callFunction(', clickFn, ', this); return false;">' + 205 '<span>' + 206 '<span id="' + id + '_text" class="cke_text cke_inline_label">' + this.label + '</span>' + 207 '</span>' + 208 '<span class=cke_openbutton><span class=cke_icon>' + ( CKEDITOR.env.hc ? '▼' : CKEDITOR.env.air ? ' ' : '' ) + '</span></span>' + // BLACK DOWN-POINTING TRIANGLE 209 '</a>' + 210 '</span>' + 211 '</span>' ); 212 213 if ( this.onRender ) 214 this.onRender(); 215 216 return instance; 217 }, 218 219 createPanel : function( editor ) 220 { 221 if ( this._.panel ) 222 return; 223 224 var panelDefinition = this._.panelDefinition, 225 panelBlockDefinition = this._.panelDefinition.block, 226 panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), 227 panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), 228 list = panel.addListBlock( this.id, panelBlockDefinition ), 229 me = this; 230 231 panel.onShow = function() 232 { 233 if ( me.className ) 234 this.element.getFirst().addClass( me.className + '_panel' ); 235 236 me.setState( CKEDITOR.TRISTATE_ON ); 237 238 list.focus( !me.multiSelect && me.getValue() ); 239 240 me._.on = 1; 241 242 if ( me.onOpen ) 243 me.onOpen(); 244 }; 245 246 panel.onHide = function( preventOnClose ) 247 { 248 if ( me.className ) 249 this.element.getFirst().removeClass( me.className + '_panel' ); 250 251 me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); 252 253 me._.on = 0; 254 255 if ( !preventOnClose && me.onClose ) 256 me.onClose(); 257 }; 258 259 panel.onEscape = function() 260 { 261 panel.hide(); 262 }; 263 264 list.onClick = function( value, marked ) 265 { 266 // Move the focus to the main windows, otherwise it will stay 267 // into the floating panel, even if invisible, and Safari and 268 // Opera will go a bit crazy. 269 me.document.getWindow().focus(); 270 271 if ( me.onClick ) 272 me.onClick.call( me, value, marked ); 273 274 if ( marked ) 275 me.setValue( value, me._.items[ value ] ); 276 else 277 me.setValue( '' ); 278 279 panel.hide( false ); 280 }; 281 282 this._.panel = panel; 283 this._.list = list; 284 285 panel.getBlock( this.id ).onHide = function() 286 { 287 me._.on = 0; 288 me.setState( CKEDITOR.TRISTATE_OFF ); 289 }; 290 291 if ( this.init ) 292 this.init(); 293 }, 294 295 setValue : function( value, text ) 296 { 297 this._.value = value; 298 299 var textElement = this.document.getById( 'cke_' + this.id + '_text' ); 300 if ( textElement ) 301 { 302 if ( !( value || text ) ) 303 { 304 text = this.label; 305 textElement.addClass( 'cke_inline_label' ); 306 } 307 else 308 textElement.removeClass( 'cke_inline_label' ); 309 310 textElement.setHtml( typeof text != 'undefined' ? text : value ); 311 } 312 }, 313 314 getValue : function() 315 { 316 return this._.value || ''; 317 }, 318 319 unmarkAll : function() 320 { 321 this._.list.unmarkAll(); 322 }, 323 324 mark : function( value ) 325 { 326 this._.list.mark( value ); 327 }, 328 329 hideItem : function( value ) 330 { 331 this._.list.hideItem( value ); 332 }, 333 334 hideGroup : function( groupTitle ) 335 { 336 this._.list.hideGroup( groupTitle ); 337 }, 338 339 showAll : function() 340 { 341 this._.list.showAll(); 342 }, 343 344 add : function( value, html, text ) 345 { 346 this._.items[ value ] = text || value; 347 this._.list.add( value, html, text ); 348 }, 349 350 startGroup : function( title ) 351 { 352 this._.list.startGroup( title ); 353 }, 354 355 commit : function() 356 { 357 if ( !this._.committed ) 358 { 359 this._.list.commit(); 360 this._.committed = 1; 361 CKEDITOR.ui.fire( 'ready', this ); 362 } 363 this._.committed = 1; 364 }, 365 366 setState : function( state ) 367 { 368 if ( this._.state == state ) 369 return; 370 371 this.document.getById( 'cke_' + this.id ).setState( state ); 372 373 this._.state = state; 374 } 375 } 376 }); 377 378 CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) 379 { 380 this.add( name, CKEDITOR.UI_RICHCOMBO, definition ); 381 }; 382