JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.1
[ckeditor.git] / _source / plugins / panel / plugin.js
1 /*\r
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 CKEDITOR.plugins.add( 'panel',\r
7 {\r
8         beforeInit : function( editor )\r
9         {\r
10                 editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler );\r
11         }\r
12 });\r
13 \r
14 /**\r
15  * Panel UI element.\r
16  * @constant\r
17  * @example\r
18  */\r
19 CKEDITOR.UI_PANEL = 'panel';\r
20 \r
21 CKEDITOR.ui.panel = function( document, definition )\r
22 {\r
23         // Copy all definition properties to this object.\r
24         if ( definition )\r
25                 CKEDITOR.tools.extend( this, definition );\r
26 \r
27         // Set defaults.\r
28         CKEDITOR.tools.extend( this,\r
29                 {\r
30                         className : '',\r
31                         css : []\r
32                 });\r
33 \r
34         this.id = CKEDITOR.tools.getNextId();\r
35         this.document = document;\r
36 \r
37         this._ =\r
38         {\r
39                 blocks : {}\r
40         };\r
41 };\r
42 \r
43 /**\r
44  * Transforms a rich combo definition in a {@link CKEDITOR.ui.richCombo}\r
45  * instance.\r
46  * @type Object\r
47  * @example\r
48  */\r
49 CKEDITOR.ui.panel.handler =\r
50 {\r
51         create : function( definition )\r
52         {\r
53                 return new CKEDITOR.ui.panel( definition );\r
54         }\r
55 };\r
56 \r
57 CKEDITOR.ui.panel.prototype =\r
58 {\r
59         renderHtml : function( editor )\r
60         {\r
61                 var output = [];\r
62                 this.render( editor, output );\r
63                 return output.join( '' );\r
64         },\r
65 \r
66         /**\r
67          * Renders the combo.\r
68          * @param {CKEDITOR.editor} editor The editor instance which this button is\r
69          *              to be used by.\r
70          * @param {Array} output The output array to which append the HTML relative\r
71          *              to this button.\r
72          * @example\r
73          */\r
74         render : function( editor, output )\r
75         {\r
76                 var id = this.id;\r
77 \r
78                 output.push(\r
79                         '<div class="', editor.skinClass ,'"' +\r
80                                 ' lang="', editor.langCode, '"' +\r
81                                 ' role="presentation"' +\r
82                                 // iframe loading need sometime, keep the panel hidden(#4186).\r
83                                 ' style="display:none;z-index:' + ( editor.config.baseFloatZIndex + 1 ) + '">' +\r
84                                 '<div' +\r
85                                         ' id=', id,\r
86                                         ' dir=', editor.lang.dir,\r
87                                         ' role="presentation"' +\r
88                                         ' class="cke_panel cke_', editor.lang.dir );\r
89 \r
90                 if ( this.className )\r
91                         output.push( ' ', this.className );\r
92 \r
93                 output.push(\r
94                                 '">' );\r
95 \r
96                 if ( this.forceIFrame || this.css.length )\r
97                 {\r
98                         output.push(\r
99                                                 '<iframe id="', id, '_frame"' +\r
100                                                         ' frameborder="0"' +\r
101                                                         ' role="application" src="javascript:void(' );\r
102 \r
103                         output.push(\r
104                                                         // Support for custom document.domain in IE.\r
105                                                         CKEDITOR.env.isCustomDomain() ?\r
106                                                                 '(function(){' +\r
107                                                                         'document.open();' +\r
108                                                                         'document.domain=\'' + document.domain + '\';' +\r
109                                                                         'document.close();' +\r
110                                                                 '})()'\r
111                                                         :\r
112                                                                 '0' );\r
113 \r
114                         output.push(\r
115                                                 ')"></iframe>' );\r
116                 }\r
117 \r
118                 output.push(\r
119                                 '</div>' +\r
120                         '</div>' );\r
121 \r
122                 return id;\r
123         },\r
124 \r
125         getHolderElement : function()\r
126         {\r
127                 var holder = this._.holder;\r
128 \r
129                 if ( !holder )\r
130                 {\r
131                         if ( this.forceIFrame || this.css.length )\r
132                         {\r
133                                 var iframe = this.document.getById( this.id + '_frame' ),\r
134                                         parentDiv = iframe.getParent(),\r
135                                         dir = parentDiv.getAttribute( 'dir' ),\r
136                                         className = parentDiv.getParent().getAttribute( 'class' ),\r
137                                         langCode = parentDiv.getParent().getAttribute( 'lang' ),\r
138                                         doc = iframe.getFrameDocument();\r
139 \r
140                                 var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function( ev )\r
141                                         {\r
142                                                 this.isLoaded = true;\r
143                                                 if ( this.onLoad )\r
144                                                         this.onLoad();\r
145                                         }, this ) );\r
146 \r
147                                 var data =\r
148                                         '<!DOCTYPE html>' +\r
149                                         '<html dir="' + dir + '" class="' + className + '_container" lang="' + langCode + '">' +\r
150                                                 '<head>' +\r
151                                                         '<style>.' + className + '_container{visibility:hidden}</style>' +\r
152                                                 '</head>' +\r
153                                                 '<body class="cke_' + dir + ' cke_panel_frame ' + CKEDITOR.env.cssClass + '" style="margin:0;padding:0"' +\r
154                                                 ' onload="( window.CKEDITOR || window.parent.CKEDITOR ).tools.callFunction(' + onLoad + ');"></body>' +\r
155                                                 // It looks strange, but for FF2, the styles must go\r
156                                                 // after <body>, so it (body) becames immediatelly\r
157                                                 // available. (#3031)\r
158                                                 CKEDITOR.tools.buildStyleHtml( this.css ) +\r
159                                         '<\/html>';\r
160 \r
161                                 doc.write( data );\r
162 \r
163                                 var win = doc.getWindow();\r
164 \r
165                                 // Register the CKEDITOR global.\r
166                                 win.$.CKEDITOR = CKEDITOR;\r
167 \r
168                                 // Arrow keys for scrolling is only preventable with 'keypress' event in Opera (#4534).\r
169                                 doc.on( 'key' + ( CKEDITOR.env.opera? 'press':'down' ), function( evt )\r
170                                         {\r
171                                                 var keystroke = evt.data.getKeystroke(),\r
172                                                         dir = this.document.getById( this.id ).getAttribute( 'dir' );\r
173 \r
174                                                 // Delegate key processing to block.\r
175                                                 if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false )\r
176                                                 {\r
177                                                         evt.data.preventDefault();\r
178                                                         return;\r
179                                                 }\r
180 \r
181                                                 // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl)\r
182                                                 if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) )\r
183                                                 {\r
184                                                         if ( this.onEscape && this.onEscape( keystroke ) === false )\r
185                                                                 evt.data.preventDefault();\r
186                                                 }\r
187                                         },\r
188                                         this );\r
189 \r
190                                 holder = doc.getBody();\r
191                                 holder.unselectable();\r
192                                 CKEDITOR.env.air && CKEDITOR.tools.callFunction( onLoad );\r
193                         }\r
194                         else\r
195                                 holder = this.document.getById( this.id );\r
196 \r
197                         this._.holder = holder;\r
198                 }\r
199 \r
200                 return holder;\r
201         },\r
202 \r
203         addBlock : function( name, block )\r
204         {\r
205                 block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ?  block\r
206                                 : new CKEDITOR.ui.panel.block( this.getHolderElement(), block );\r
207 \r
208                 if ( !this._.currentBlock )\r
209                         this.showBlock( name );\r
210 \r
211                 return block;\r
212         },\r
213 \r
214         getBlock : function( name )\r
215         {\r
216                 return this._.blocks[ name ];\r
217         },\r
218 \r
219         showBlock : function( name )\r
220         {\r
221                 var blocks = this._.blocks,\r
222                         block = blocks[ name ],\r
223                         current = this._.currentBlock,\r
224                         holder = this.forceIFrame ?\r
225                                 this.document.getById( this.id + '_frame' )\r
226                                 : this._.holder;\r
227 \r
228                 // Disable context menu for block panel.\r
229                 holder.getParent().getParent().disableContextMenu();\r
230 \r
231                 if ( current )\r
232                 {\r
233                         // Clean up the current block's effects on holder.\r
234                         holder.removeAttributes( current.attributes );\r
235                         current.hide();\r
236                 }\r
237 \r
238                 this._.currentBlock = block;\r
239 \r
240                 holder.setAttributes( block.attributes );\r
241                 CKEDITOR.fire( 'ariaWidget', holder );\r
242 \r
243                 // Reset the focus index, so it will always go into the first one.\r
244                 block._.focusIndex = -1;\r
245 \r
246                 this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block );\r
247 \r
248                 block.onMark = function( item )\r
249                 {\r
250                         holder.setAttribute( 'aria-activedescendant', item.getId() + '_option' );\r
251                 };\r
252 \r
253                 block.onUnmark = function()\r
254                 {\r
255                         holder.removeAttribute( 'aria-activedescendant' );\r
256                 };\r
257 \r
258                 block.show();\r
259 \r
260                 return block;\r
261         },\r
262 \r
263         destroy : function()\r
264         {\r
265                 this.element && this.element.remove();\r
266         }\r
267 };\r
268 \r
269 CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass(\r
270 {\r
271         $ : function( blockHolder, blockDefinition )\r
272         {\r
273                 this.element = blockHolder.append(\r
274                         blockHolder.getDocument().createElement( 'div',\r
275                                 {\r
276                                         attributes :\r
277                                         {\r
278                                                 'tabIndex' : -1,\r
279                                                 'class' : 'cke_panel_block',\r
280                                                 'role' : 'presentation'\r
281                                         },\r
282                                         styles :\r
283                                         {\r
284                                                 display : 'none'\r
285                                         }\r
286                                 }) );\r
287 \r
288                 // Copy all definition properties to this object.\r
289                 if ( blockDefinition )\r
290                         CKEDITOR.tools.extend( this, blockDefinition );\r
291 \r
292                 if ( !this.attributes.title )\r
293                         this.attributes.title = this.attributes[ 'aria-label' ];\r
294 \r
295                 this.keys = {};\r
296 \r
297                 this._.focusIndex = -1;\r
298 \r
299                 // Disable context menu for panels.\r
300                 this.element.disableContextMenu();\r
301         },\r
302 \r
303         _ : {\r
304 \r
305                 /**\r
306                  * Mark the item specified by the index as current activated.\r
307                  */\r
308                 markItem: function( index )\r
309                 {\r
310                         if ( index == -1 )\r
311                                 return;\r
312                         var links = this.element.getElementsByTag( 'a' );\r
313                         var item = links.getItem( this._.focusIndex = index );\r
314 \r
315                         // Safari need focus on the iframe window first(#3389), but we need\r
316                         // lock the blur to avoid hiding the panel.\r
317                         if ( CKEDITOR.env.webkit || CKEDITOR.env.opera )\r
318                                 item.getDocument().getWindow().focus();\r
319                         item.focus();\r
320 \r
321                         this.onMark && this.onMark( item );\r
322                 }\r
323         },\r
324 \r
325         proto :\r
326         {\r
327                 show : function()\r
328                 {\r
329                         this.element.setStyle( 'display', '' );\r
330                 },\r
331 \r
332                 hide : function()\r
333                 {\r
334                         if ( !this.onHide || this.onHide.call( this )  !== true )\r
335                                 this.element.setStyle( 'display', 'none' );\r
336                 },\r
337 \r
338                 onKeyDown : function( keystroke )\r
339                 {\r
340                         var keyAction = this.keys[ keystroke ];\r
341                         switch ( keyAction )\r
342                         {\r
343                                 // Move forward.\r
344                                 case 'next' :\r
345                                         var index = this._.focusIndex,\r
346                                                 links = this.element.getElementsByTag( 'a' ),\r
347                                                 link;\r
348 \r
349                                         while ( ( link = links.getItem( ++index ) ) )\r
350                                         {\r
351                                                 // Move the focus only if the element is marked with\r
352                                                 // the _cke_focus and it it's visible (check if it has\r
353                                                 // width).\r
354                                                 if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth )\r
355                                                 {\r
356                                                         this._.focusIndex = index;\r
357                                                         link.focus();\r
358                                                         break;\r
359                                                 }\r
360                                         }\r
361                                         return false;\r
362 \r
363                                 // Move backward.\r
364                                 case 'prev' :\r
365                                         index = this._.focusIndex;\r
366                                         links = this.element.getElementsByTag( 'a' );\r
367 \r
368                                         while ( index > 0 && ( link = links.getItem( --index ) ) )\r
369                                         {\r
370                                                 // Move the focus only if the element is marked with\r
371                                                 // the _cke_focus and it it's visible (check if it has\r
372                                                 // width).\r
373                                                 if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth )\r
374                                                 {\r
375                                                         this._.focusIndex = index;\r
376                                                         link.focus();\r
377                                                         break;\r
378                                                 }\r
379                                         }\r
380                                         return false;\r
381 \r
382                                 case 'click' :\r
383                                 case 'mouseup' :\r
384                                         index = this._.focusIndex;\r
385                                         link = index >= 0 && this.element.getElementsByTag( 'a' ).getItem( index );\r
386 \r
387                                         if ( link )\r
388                                                 link.$[ keyAction ] ? link.$[ keyAction ]() : link.$[ 'on' + keyAction ]();\r
389 \r
390                                         return false;\r
391                         }\r
392 \r
393                         return true;\r
394                 }\r
395         }\r
396 });\r
397 \r
398 /**\r
399  * Fired when a panel is added to the document\r
400  * @name CKEDITOR#ariaWidget\r
401  * @event\r
402  * @param {Object} holder The element wrapping the panel\r
403  */\r