JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.1
[ckeditor.git] / _source / plugins / div / dialogs / div.js
1 /*\r
2  * Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3  * For licensing, see LICENSE.html or http://ckeditor.com/license\r
4  */\r
5 \r
6 (function()\r
7 {\r
8 \r
9         /**\r
10          * Add to collection with DUP examination.\r
11          * @param {Object} collection\r
12          * @param {Object} element\r
13          * @param {Object} database\r
14          */\r
15         function addSafely( collection, element, database )\r
16         {\r
17                 // 1. IE doesn't support customData on text nodes;\r
18                 // 2. Text nodes never get chance to appear twice;\r
19                 if ( !element.is || !element.getCustomData( 'block_processed' ) )\r
20                 {\r
21                         element.is && CKEDITOR.dom.element.setMarker( database, element, 'block_processed', true );\r
22                         collection.push( element );\r
23                 }\r
24         }\r
25 \r
26         function getNonEmptyChildren( element )\r
27         {\r
28                 var retval = [];\r
29                 var children = element.getChildren();\r
30                 for( var i = 0 ; i < children.count() ; i++ )\r
31                 {\r
32                         var child = children.getItem( i );\r
33                         if( ! ( child.type === CKEDITOR.NODE_TEXT\r
34                                 && ( /^[ \t\n\r]+$/ ).test( child.getText() ) ) )\r
35                                 retval.push( child );\r
36                 }\r
37                 return retval;\r
38         }\r
39 \r
40 \r
41         /**\r
42          * Dialog reused by both 'creatediv' and 'editdiv' commands.\r
43          * @param {Object} editor\r
44          * @param {String} command      The command name which indicate what the current command is.\r
45          */\r
46         function divDialog( editor, command )\r
47         {\r
48                 // Definition of elements at which div operation should stopped.\r
49                 var divLimitDefinition = ( function(){\r
50 \r
51                         // Customzie from specialize blockLimit elements\r
52                         var definition = CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$blockLimit );\r
53 \r
54                         // Exclude 'div' itself.\r
55                         delete definition.div;\r
56 \r
57                         // Exclude 'td' and 'th' when 'wrapping table'\r
58                         if( editor.config.div_wrapTable )\r
59                         {\r
60                                 delete definition.td;\r
61                                 delete definition.th;\r
62                         }\r
63                         return definition;\r
64                 })();\r
65 \r
66                 // DTD of 'div' element\r
67                 var dtd = CKEDITOR.dtd.div;\r
68 \r
69                 /**\r
70                  * Get the first div limit element on the element's path.\r
71                  * @param {Object} element\r
72                  */\r
73                 function getDivLimitElement( element )\r
74                 {\r
75                         var pathElements = new CKEDITOR.dom.elementPath( element ).elements;\r
76                         var divLimit;\r
77                         for ( var i = 0; i < pathElements.length ; i++ )\r
78                         {\r
79                                 if ( pathElements[ i ].getName() in divLimitDefinition )\r
80                                 {\r
81                                         divLimit = pathElements[ i ];\r
82                                         break;\r
83                                 }\r
84                         }\r
85                         return divLimit;\r
86                 }\r
87 \r
88                 /**\r
89                  * Init all fields' setup/commit function.\r
90                  * @memberof divDialog\r
91                  */\r
92                 function setupFields()\r
93                 {\r
94                         this.foreach( function( field )\r
95                         {\r
96                                 // Exclude layout container elements\r
97                                 if( /^(?!vbox|hbox)/.test( field.type ) )\r
98                                 {\r
99                                         if ( !field.setup )\r
100                                         {\r
101                                                 // Read the dialog fields values from the specified\r
102                                                 // element attributes.\r
103                                                 field.setup = function( element )\r
104                                                 {\r
105                                                         field.setValue( element.getAttribute( field.id ) || '' );\r
106                                                 };\r
107                                         }\r
108                                         if ( !field.commit )\r
109                                         {\r
110                                                 // Set element attributes assigned by the dialog\r
111                                                 // fields.\r
112                                                 field.commit = function( element )\r
113                                                 {\r
114                                                         var fieldValue = this.getValue();\r
115                                                         // ignore default element attribute values\r
116                                                         if ( 'dir' == field.id && element.getComputedStyle( 'direction' ) == fieldValue )\r
117                                                                 return;\r
118 \r
119                                                         if ( fieldValue )\r
120                                                                 element.setAttribute( field.id, fieldValue );\r
121                                                         else\r
122                                                                 element.removeAttribute( field.id );\r
123                                                 };\r
124                                         }\r
125                                 }\r
126                         } );\r
127                 }\r
128 \r
129                 /**\r
130                  * Wrapping 'div' element around appropriate blocks among the selected ranges.\r
131                  * @param {Object} editor\r
132                  */\r
133                 function createDiv( editor )\r
134                 {\r
135                         // new adding containers OR detected pre-existed containers.\r
136                         var containers = [];\r
137                         // node markers store.\r
138                         var database = {};\r
139                         // All block level elements which contained by the ranges.\r
140                         var containedBlocks = [], block;\r
141 \r
142                         // Get all ranges from the selection.\r
143                         var selection = editor.document.getSelection();\r
144                         var ranges = selection.getRanges();\r
145                         var bookmarks = selection.createBookmarks();\r
146                         var i, iterator;\r
147 \r
148                         // Calcualte a default block tag if we need to create blocks.\r
149                         var blockTag = editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p';\r
150 \r
151                         // collect all included elements from dom-iterator\r
152                         for( i = 0 ; i < ranges.length ; i++ )\r
153                         {\r
154                                 iterator = ranges[ i ].createIterator();\r
155                                 while( ( block = iterator.getNextParagraph() ) )\r
156                                 {\r
157                                         // include contents of blockLimit elements.\r
158                                         if( block.getName() in divLimitDefinition )\r
159                                         {\r
160                                                 var j, childNodes = block.getChildren();\r
161                                                 for ( j = 0 ; j < childNodes.count() ; j++ )\r
162                                                         addSafely( containedBlocks, childNodes.getItem( j ) , database );\r
163                                         }\r
164                                         else\r
165                                         {\r
166                                                 // Bypass dtd disallowed elements.\r
167                                                 while( !dtd[ block.getName() ] && block.getName() != 'body' )\r
168                                                         block = block.getParent();\r
169                                                 addSafely( containedBlocks, block, database );\r
170                                         }\r
171                                 }\r
172                         }\r
173 \r
174                         CKEDITOR.dom.element.clearAllMarkers( database );\r
175 \r
176                         var blockGroups = groupByDivLimit( containedBlocks );\r
177                         var ancestor, blockEl, divElement;\r
178 \r
179                         for( i = 0 ; i < blockGroups.length ; i++ )\r
180                         {\r
181                                 var currentNode = blockGroups[ i ][ 0 ];\r
182 \r
183                                 // Calculate the common parent node of all contained elements.\r
184                                 ancestor = currentNode.getParent();\r
185                                 for ( j = 1 ; j < blockGroups[ i ].length; j++ )\r
186                                         ancestor = ancestor.getCommonAncestor( blockGroups[ i ][ j ] );\r
187 \r
188                                 divElement = new CKEDITOR.dom.element( 'div', editor.document );\r
189 \r
190                                 // Normalize the blocks in each group to a common parent.\r
191                                 for( j = 0; j < blockGroups[ i ].length ; j++ )\r
192                                 {\r
193                                         currentNode = blockGroups[ i ][ j ];\r
194 \r
195                                         while( !currentNode.getParent().equals( ancestor ) )\r
196                                                 currentNode = currentNode.getParent();\r
197 \r
198                                         // This could introduce some duplicated elements in array.\r
199                                         blockGroups[ i ][ j ] = currentNode;\r
200                                 }\r
201 \r
202                                 // Wrapped blocks counting\r
203                                 var fixedBlock = null;\r
204                                 for ( j = 0 ; j < blockGroups[ i ].length ; j++ )\r
205                                 {\r
206                                         currentNode = blockGroups[ i ][ j ];\r
207 \r
208                                         // Avoid DUP elements introduced by grouping.\r
209                                         if ( !( currentNode.getCustomData && currentNode.getCustomData( 'block_processed' ) ) )\r
210                                         {\r
211                                                 currentNode.is && CKEDITOR.dom.element.setMarker( database, currentNode, 'block_processed', true );\r
212 \r
213                                                 // Establish new container, wrapping all elements in this group.\r
214                                                 if ( !j )\r
215                                                         divElement.insertBefore( currentNode );\r
216 \r
217                                                 divElement.append( currentNode );\r
218                                         }\r
219                                 }\r
220 \r
221                                 CKEDITOR.dom.element.clearAllMarkers( database );\r
222                                 containers.push( divElement );\r
223                         }\r
224 \r
225                         selection.selectBookmarks( bookmarks );\r
226                         return containers;\r
227                 }\r
228 \r
229                 function getDiv( editor )\r
230                 {\r
231                         var path = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ),\r
232                                 blockLimit = path.blockLimit,\r
233                                 div = blockLimit && blockLimit.getAscendant( 'div', true );\r
234                         return div;\r
235                 }\r
236                 /**\r
237                  * Divide a set of nodes to different groups by their path's blocklimit element.\r
238                  * Note: the specified nodes should be in source order naturally, which mean they are supposed to producea by following class:\r
239                  *  * CKEDITOR.dom.range.Iterator\r
240                  *  * CKEDITOR.dom.domWalker\r
241                  *  @return {Array []} the grouped nodes\r
242                  */\r
243                 function groupByDivLimit( nodes )\r
244                 {\r
245                         var groups = [],\r
246                                 lastDivLimit = null,\r
247                                 path, block;\r
248                         for ( var i = 0 ; i < nodes.length ; i++ )\r
249                         {\r
250                                 block = nodes[i];\r
251                                 var limit = getDivLimitElement( block );\r
252                                 if ( !limit.equals( lastDivLimit ) )\r
253                                 {\r
254                                         lastDivLimit = limit ;\r
255                                         groups.push( [] ) ;\r
256                                 }\r
257                                 groups[ groups.length - 1 ].push( block ) ;\r
258                         }\r
259                         return groups;\r
260                 }\r
261 \r
262                 /**\r
263                  * Hold a collection of created block container elements.\r
264                  */\r
265                 var containers = [];\r
266                 /**\r
267                  * @type divDialog\r
268                  */\r
269                 return {\r
270                         title : editor.lang.div.title,\r
271                         minWidth : 400,\r
272                         minHeight : 165,\r
273                         contents :\r
274                         [\r
275                         {\r
276                                 id :'info',\r
277                                 label :editor.lang.common.generalTab,\r
278                                 title :editor.lang.common.generalTab,\r
279                                 elements :\r
280                                 [\r
281                                         {\r
282                                                 type :'hbox',\r
283                                                 widths : [ '50%', '50%' ],\r
284                                                 children :\r
285                                                 [\r
286                                                         {\r
287                                                                 id :'elementStyle',\r
288                                                                 type :'select',\r
289                                                                 style :'width: 100%;',\r
290                                                                 label :editor.lang.div.styleSelectLabel,\r
291                                                                 'default' : '',\r
292                                                                 items : [],\r
293                                                                 setup : function( element )\r
294                                                                 {\r
295                                                                         this.setValue( element.$.style.cssText || '' );\r
296                                                                 },\r
297                                                                 commit: function( element )\r
298                                                                 {\r
299                                                                         if ( this.getValue() )\r
300                                                                                 element.$.style.cssText = this.getValue();\r
301                                                                         else\r
302                                                                                 element.removeAttribute( 'style' );\r
303                                                                 }\r
304                                                         },\r
305                                                         {\r
306                                                                 id :'class',\r
307                                                                 type :'text',\r
308                                                                 label :editor.lang.common.cssClass,\r
309                                                                 'default' : ''\r
310                                                         }\r
311                                                 ]\r
312                                         }\r
313                                 ]\r
314                         },\r
315                         {\r
316                                         id :'advanced',\r
317                                         label :editor.lang.common.advancedTab,\r
318                                         title :editor.lang.common.advancedTab,\r
319                                         elements :\r
320                                         [\r
321                                         {\r
322                                                 type :'vbox',\r
323                                                 padding :1,\r
324                                                 children :\r
325                                                 [\r
326                                                         {\r
327                                                                 type :'hbox',\r
328                                                                 widths : [ '50%', '50%' ],\r
329                                                                 children :\r
330                                                                 [\r
331                                                                         {\r
332                                                                                 type :'text',\r
333                                                                                 id :'id',\r
334                                                                                 label :editor.lang.common.id,\r
335                                                                                 'default' : ''\r
336                                                                         },\r
337                                                                         {\r
338                                                                                 type :'text',\r
339                                                                                 id :'lang',\r
340                                                                                 label :editor.lang.link.langCode,\r
341                                                                                 'default' : ''\r
342                                                                         }\r
343                                                                 ]\r
344                                                         },\r
345                                                         {\r
346                                                                 type :'hbox',\r
347                                                                 children :\r
348                                                                 [\r
349                                                                                 {\r
350                                                                                         type :'text',\r
351                                                                                         id :'style',\r
352                                                                                         style :'width: 100%;',\r
353                                                                                         label :editor.lang.common.cssStyle,\r
354                                                                                         'default' : ''\r
355                                                                                 }\r
356                                                                 ]\r
357                                                         },\r
358                                                         {\r
359                                                                 type :'hbox',\r
360                                                                 children :\r
361                                                                 [\r
362                                                                                 {\r
363                                                                                         type :'text',\r
364                                                                                         id :'title',\r
365                                                                                         style :'width: 100%;',\r
366                                                                                         label :editor.lang.common.advisoryTitle,\r
367                                                                                         'default' : ''\r
368                                                                                 }\r
369                                                                 ]\r
370                                                         },\r
371                                                         {\r
372                                                                 type :'select',\r
373                                                                 id :'dir',\r
374                                                                 style :'width: 100%;',\r
375                                                                 label :editor.lang.common.langDir,\r
376                                                                 'default' : '',\r
377                                                                 items :\r
378                                                                 [\r
379                                                                         [\r
380                                                                                 editor.lang.common.langDirLtr,\r
381                                                                                 'ltr'\r
382                                                                         ],\r
383                                                                         [\r
384                                                                                 editor.lang.common.langDirRtl,\r
385                                                                                 'rtl'\r
386                                                                         ]\r
387                                                                 ]\r
388                                                         }\r
389                                                 ]\r
390                                         }\r
391                                         ]\r
392                                 }\r
393                         ],\r
394                         onLoad : function()\r
395                         {\r
396                                 setupFields.call(this);\r
397                         },\r
398                         onShow : function()\r
399                         {\r
400                                 // Whether always create new container regardless of existed\r
401                                 // ones.\r
402                                 if ( command == 'editdiv' )\r
403                                 {\r
404                                         // Try to discover the containers that already existed in\r
405                                         // ranges\r
406                                         var div = getDiv( editor );\r
407                                         // update dialog field values\r
408                                         div && this.setupContent( this._element = div );\r
409                                 }\r
410                         },\r
411                         onOk : function()\r
412                         {\r
413                                 if( command == 'editdiv' )\r
414                                         containers = [ this._element ];\r
415                                 else\r
416                                         containers = createDiv( editor, true );\r
417 \r
418                                 // Update elements attributes\r
419                                 for( var i = 0 ; i < containers.length ; i++ )\r
420                                         this.commitContent( containers[ i ] );\r
421                                 this.hide();\r
422                         }\r
423                 };\r
424         }\r
425 \r
426         CKEDITOR.dialog.add( 'creatediv', function( editor )\r
427                 {\r
428                         return divDialog( editor, 'creatediv' );\r
429                 } );\r
430         CKEDITOR.dialog.add( 'editdiv', function( editor )\r
431                 {\r
432                         return divDialog( editor, 'editdiv' );\r
433                 } );\r
434 } )();\r
435 \r
436 /*\r
437  * @name CKEDITOR.config.div_wrapTable\r
438  * Whether to wrap the whole table instead of indivisual cells when created 'div' in table cell.\r
439  * @type Boolean\r
440  * @default false\r
441  * @example config.div_wrapTable = true;\r
442  */\r