JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.1
[ckeditor.git] / _source / plugins / tableresize / 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 (function()\r
7 {\r
8         var pxUnit = CKEDITOR.tools.cssLength,\r
9                 needsIEHacks = CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks || CKEDITOR.env.version < 7 );\r
10 \r
11         function getWidth( el )\r
12         {\r
13                 return CKEDITOR.env.ie ? el.$.clientWidth : parseInt( el.getComputedStyle( 'width' ), 10 );\r
14         }\r
15 \r
16         function getBorderWidth( element, side )\r
17         {\r
18                 var computed = element.getComputedStyle( 'border-' + side + '-width' ),\r
19                         borderMap =\r
20                         {\r
21                                 thin: '0px',\r
22                                 medium: '1px',\r
23                                 thick: '2px'\r
24                         };\r
25 \r
26                 if ( computed.indexOf( 'px' ) < 0 )\r
27                 {\r
28                         // look up keywords\r
29                         if ( computed in borderMap && element.getComputedStyle( 'border-style' ) != 'none' )\r
30                                 computed = borderMap[ computed ];\r
31                         else\r
32                                 computed = 0;\r
33                 }\r
34 \r
35                 return parseInt( computed, 10 );\r
36         }\r
37 \r
38         // Gets the table row that contains the most columns.\r
39         function getMasterPillarRow( table )\r
40         {\r
41                 var $rows = table.$.rows,\r
42                         maxCells = 0, cellsCount,\r
43                         $elected, $tr;\r
44 \r
45                 for ( var i = 0, len = $rows.length ; i < len; i++ )\r
46                 {\r
47                         $tr = $rows[ i ];\r
48                         cellsCount = $tr.cells.length;\r
49 \r
50                         if ( cellsCount > maxCells )\r
51                         {\r
52                                 maxCells = cellsCount;\r
53                                 $elected = $tr;\r
54                         }\r
55                 }\r
56 \r
57                 return $elected;\r
58         }\r
59 \r
60         function buildTableColumnPillars( table )\r
61         {\r
62                 var pillars = [],\r
63                         pillarIndex = -1,\r
64                         rtl = ( table.getComputedStyle( 'direction' ) == 'rtl' );\r
65 \r
66                 // Get the raw row element that cointains the most columns.\r
67                 var $tr = getMasterPillarRow( table );\r
68 \r
69                 // Get the tbody element and position, which will be used to set the\r
70                 // top and bottom boundaries.\r
71                 var tbody = new CKEDITOR.dom.element( table.$.tBodies[ 0 ] ),\r
72                         tbodyPosition = tbody.getDocumentPosition();\r
73 \r
74                 // Loop thorugh all cells, building pillars after each one of them.\r
75                 for ( var i = 0, len = $tr.cells.length ; i < len ; i++ )\r
76                 {\r
77                         // Both the current cell and the successive one will be used in the\r
78                         // pillar size calculation.\r
79                         var td = new CKEDITOR.dom.element( $tr.cells[ i ] ),\r
80                                 nextTd = $tr.cells[ i + 1 ] && new CKEDITOR.dom.element( $tr.cells[ i + 1 ] );\r
81 \r
82                         pillarIndex += td.$.colSpan || 1;\r
83 \r
84                         // Calculate the pillar boundary positions.\r
85                         var pillarLeft, pillarRight, pillarWidth;\r
86 \r
87                         var x = td.getDocumentPosition().x;\r
88 \r
89                         // Calculate positions based on the current cell.\r
90                         rtl ?\r
91                                 pillarRight = x + getBorderWidth( td, 'left' ) :\r
92                                 pillarLeft  = x + td.$.offsetWidth - getBorderWidth( td, 'right' );\r
93 \r
94                         // Calculate positions based on the next cell, if available.\r
95                         if ( nextTd )\r
96                         {\r
97                                 x =  nextTd.getDocumentPosition().x;\r
98 \r
99                                 rtl ?\r
100                                         pillarLeft      = x + nextTd.$.offsetWidth - getBorderWidth( nextTd, 'right' ) :\r
101                                         pillarRight     = x + getBorderWidth( nextTd, 'left' );\r
102                         }\r
103                         // Otherwise calculate positions based on the table (for last cell).\r
104                         else\r
105                         {\r
106                                 x =  table.getDocumentPosition().x;\r
107 \r
108                                 rtl ?\r
109                                         pillarLeft      = x :\r
110                                         pillarRight     = x + table.$.offsetWidth;\r
111                         }\r
112 \r
113                         pillarWidth = Math.max( pillarRight - pillarLeft, 3 );\r
114 \r
115                         // The pillar should reflects exactly the shape of the hovered\r
116                         // column border line.\r
117                         pillars.push( {\r
118                                 table : table,\r
119                                 index : pillarIndex,\r
120                                 x : pillarLeft,\r
121                                 y : tbodyPosition.y,\r
122                                 width : pillarWidth,\r
123                                 height : tbody.$.offsetHeight,\r
124                                 rtl : rtl } );\r
125                 }\r
126 \r
127                 return pillars;\r
128         }\r
129 \r
130         function getPillarAtPosition( pillars, positionX )\r
131         {\r
132                 for ( var i = 0, len = pillars.length ; i < len ; i++ )\r
133                 {\r
134                         var pillar = pillars[ i ];\r
135 \r
136                         if ( positionX >= pillar.x && positionX <= ( pillar.x + pillar.width ) )\r
137                                 return pillar;\r
138                 }\r
139 \r
140                 return null;\r
141         }\r
142 \r
143         function cancel( evt )\r
144         {\r
145                 ( evt.data || evt ).preventDefault();\r
146         }\r
147 \r
148         function columnResizer( editor )\r
149         {\r
150                 var pillar,\r
151                         document,\r
152                         resizer,\r
153                         isResizing,\r
154                         startOffset,\r
155                         currentShift;\r
156 \r
157                 var leftSideCells, rightSideCells, leftShiftBoundary, rightShiftBoundary;\r
158 \r
159                 function detach()\r
160                 {\r
161                         pillar = null;\r
162                         currentShift = 0;\r
163                         isResizing = 0;\r
164 \r
165                         document.removeListener( 'mouseup', onMouseUp );\r
166                         resizer.removeListener( 'mousedown', onMouseDown );\r
167                         resizer.removeListener( 'mousemove', onMouseMove );\r
168 \r
169                         document.getBody().setStyle( 'cursor', 'auto' );\r
170 \r
171                         // Hide the resizer (remove it on IE7 - #5890).\r
172                         needsIEHacks ? resizer.remove() : resizer.hide();\r
173                 }\r
174 \r
175                 function resizeStart()\r
176                 {\r
177                         // Before starting to resize, figure out which cells to change\r
178                         // and the boundaries of this resizing shift.\r
179 \r
180                         var columnIndex = pillar.index,\r
181                                 map = CKEDITOR.tools.buildTableMap( pillar.table ),\r
182                                 leftColumnCells = [],\r
183                                 rightColumnCells = [],\r
184                                 leftMinSize = Number.MAX_VALUE,\r
185                                 rightMinSize = leftMinSize,\r
186                                 rtl = pillar.rtl;\r
187 \r
188                         for ( var i = 0, len = map.length ; i < len ; i++ )\r
189                         {\r
190                                 var row                 = map[ i ],\r
191                                         leftCell        = row[ columnIndex + ( rtl ? 1 : 0 ) ],\r
192                                         rightCell       = row[ columnIndex + ( rtl ? 0 : 1 ) ];\r
193 \r
194                                 leftCell        = leftCell && new CKEDITOR.dom.element( leftCell );\r
195                                 rightCell       = rightCell && new CKEDITOR.dom.element( rightCell );\r
196 \r
197                                 if ( !leftCell || !rightCell || !leftCell.equals( rightCell ) )\r
198                                 {\r
199                                         leftCell && ( leftMinSize = Math.min( leftMinSize, getWidth( leftCell ) ) );\r
200                                         rightCell && ( rightMinSize = Math.min( rightMinSize, getWidth( rightCell ) ) );\r
201 \r
202                                         leftColumnCells.push( leftCell );\r
203                                         rightColumnCells.push( rightCell );\r
204                                 }\r
205                         }\r
206 \r
207                         // Cache the list of cells to be resized.\r
208                         leftSideCells = leftColumnCells;\r
209                         rightSideCells = rightColumnCells;\r
210 \r
211                         // Cache the resize limit boundaries.\r
212                         leftShiftBoundary =  pillar.x - leftMinSize;\r
213                         rightShiftBoundary = pillar.x + rightMinSize;\r
214 \r
215                         resizer.setOpacity( 0.5 );\r
216                         startOffset = parseInt( resizer.getStyle( 'left' ), 10 );\r
217                         currentShift = 0;\r
218                         isResizing = 1;\r
219 \r
220                         resizer.on( 'mousemove', onMouseMove );\r
221 \r
222                         // Prevent the native drag behavior otherwise 'mousemove' won't fire.\r
223                         document.on( 'dragstart', cancel );\r
224                 }\r
225 \r
226                 function resizeEnd()\r
227                 {\r
228                         isResizing = 0;\r
229 \r
230                         resizer.setOpacity( 0 );\r
231 \r
232                         currentShift && resizeColumn();\r
233 \r
234                         var table = pillar.table;\r
235                         setTimeout( function () { table.removeCustomData( '_cke_table_pillars' ); }, 0 );\r
236 \r
237                         document.removeListener( 'dragstart', cancel );\r
238                 }\r
239 \r
240                 function resizeColumn()\r
241                 {\r
242                         var rtl = pillar.rtl,\r
243                                 cellsCount = rtl ? rightSideCells.length : leftSideCells.length;\r
244 \r
245                         // Perform the actual resize to table cells, only for those by side of the pillar.\r
246                         for ( var i = 0 ; i < cellsCount ; i++ )\r
247                         {\r
248                                 var leftCell = leftSideCells[ i ],\r
249                                         rightCell = rightSideCells[ i ],\r
250                                         table = pillar.table;\r
251 \r
252                                 // Defer the resizing to avoid any interference among cells.\r
253                                 CKEDITOR.tools.setTimeout(\r
254                                         function( leftCell, leftOldWidth, rightCell, rightOldWidth, tableWidth, sizeShift )\r
255                                         {\r
256                                                 leftCell && leftCell.setStyle( 'width', pxUnit( Math.max( leftOldWidth + sizeShift, 0 ) ) );\r
257                                                 rightCell && rightCell.setStyle( 'width', pxUnit( Math.max( rightOldWidth - sizeShift, 0 ) ) );\r
258 \r
259                                                 // If we're in the last cell, we need to resize the table as well\r
260                                                 if ( tableWidth )\r
261                                                         table.setStyle( 'width', pxUnit( tableWidth + sizeShift * ( rtl ? -1 : 1 ) ) );\r
262                                         }\r
263                                         , 0,\r
264                                         this, [\r
265                                                 leftCell, leftCell && getWidth( leftCell ),\r
266                                                 rightCell, rightCell && getWidth( rightCell ),\r
267                                                 ( !leftCell || !rightCell ) && ( getWidth( table ) + getBorderWidth( table, 'left' ) + getBorderWidth( table, 'right' ) ),\r
268                                                 currentShift ] );\r
269                         }\r
270                 }\r
271 \r
272                 function onMouseDown( evt )\r
273                 {\r
274                         cancel( evt );\r
275 \r
276                         resizeStart();\r
277 \r
278                         document.on( 'mouseup', onMouseUp, this );\r
279                 }\r
280 \r
281                 function onMouseUp( evt )\r
282                 {\r
283                         evt.removeListener();\r
284 \r
285                         resizeEnd();\r
286                 }\r
287 \r
288                 function onMouseMove( evt )\r
289                 {\r
290                         move( evt.data.$.clientX );\r
291                 }\r
292 \r
293                 document = editor.document;\r
294 \r
295                 resizer = CKEDITOR.dom.element.createFromHtml(\r
296                         '<div data-cke-temp=1 contenteditable=false unselectable=on '+\r
297                         'style="position:absolute;cursor:col-resize;filter:alpha(opacity=0);opacity:0;' +\r
298                                 'padding:0;background-color:#004;background-image:none;border:0px none;z-index:10"></div>', document );\r
299 \r
300                 // Except on IE6/7 (#5890), place the resizer after body to prevent it\r
301                 // from being editable.\r
302                 if ( !needsIEHacks )\r
303                         document.getDocumentElement().append( resizer );\r
304 \r
305                 this.attachTo = function( targetPillar )\r
306                 {\r
307                         // Accept only one pillar at a time.\r
308                         if ( isResizing )\r
309                                 return;\r
310 \r
311                         // On IE6/7, we append the resizer everytime we need it. (#5890)\r
312                         if ( needsIEHacks )\r
313                         {\r
314                                 document.getBody().append( resizer );\r
315                                 currentShift = 0;\r
316                         }\r
317 \r
318                         pillar = targetPillar;\r
319 \r
320                         resizer.setStyles(\r
321                                 {\r
322                                         width: pxUnit( targetPillar.width ),\r
323                                         height : pxUnit( targetPillar.height ),\r
324                                         left : pxUnit( targetPillar.x ),\r
325                                         top : pxUnit( targetPillar.y )\r
326                                 });\r
327 \r
328                         // In IE6/7, it's not possible to have custom cursors for floating\r
329                         // elements in an editable document. Show the resizer in that case,\r
330                         // to give the user a visual clue.\r
331                         needsIEHacks && resizer.setOpacity( 0.25 );\r
332 \r
333                         resizer.on( 'mousedown', onMouseDown, this );\r
334 \r
335                         document.getBody().setStyle( 'cursor', 'col-resize' );\r
336 \r
337                         // Display the resizer to receive events but don't show it,\r
338                         // only change the cursor to resizable shape.\r
339                         resizer.show();\r
340                 };\r
341 \r
342                 var move = this.move = function( posX )\r
343                 {\r
344                         if ( !pillar )\r
345                                 return 0;\r
346 \r
347                         if ( !isResizing && ( posX < pillar.x || posX > ( pillar.x + pillar.width ) ) )\r
348                         {\r
349                                 detach();\r
350                                 return 0;\r
351                         }\r
352 \r
353                         var resizerNewPosition = posX - Math.round( resizer.$.offsetWidth / 2 );\r
354 \r
355                         if ( isResizing )\r
356                         {\r
357                                 if ( resizerNewPosition == leftShiftBoundary || resizerNewPosition == rightShiftBoundary )\r
358                                         return 1;\r
359 \r
360                                 resizerNewPosition = Math.max( resizerNewPosition, leftShiftBoundary );\r
361                                 resizerNewPosition = Math.min( resizerNewPosition, rightShiftBoundary );\r
362 \r
363                                 currentShift = resizerNewPosition - startOffset;\r
364                         }\r
365 \r
366                         resizer.setStyle( 'left', pxUnit( resizerNewPosition ) );\r
367 \r
368                         return 1;\r
369                 };\r
370         }\r
371 \r
372         function clearPillarsCache( evt )\r
373         {\r
374                 var target = evt.data.getTarget();\r
375 \r
376                 if ( evt.name == 'mouseout' )\r
377                 {\r
378                         // Bypass interal mouse move.\r
379                         if ( !target.is ( 'table' ) )\r
380                                 return;\r
381 \r
382                         var dest = new CKEDITOR.dom.element( evt.data.$.relatedTarget || evt.data.$.toElement );\r
383                         while( dest && dest.$ && !dest.equals( target ) && !dest.is( 'body' ) )\r
384                                 dest = dest.getParent();\r
385                         if ( !dest || dest.equals( target ) )\r
386                                 return;\r
387                 }\r
388 \r
389                 target.getAscendant( 'table', 1 ).removeCustomData( '_cke_table_pillars' );\r
390                 evt.removeListener();\r
391         }\r
392 \r
393         CKEDITOR.plugins.add( 'tableresize',\r
394         {\r
395                 requires : [ 'tabletools' ],\r
396                 init : function( editor )\r
397                 {\r
398                         editor.on( 'contentDom', function()\r
399                         {\r
400                                 var resizer;\r
401 \r
402                                 editor.document.getBody().on( 'mousemove', function( evt )\r
403                                         {\r
404                                                 evt = evt.data;\r
405 \r
406                                                 // If we're already attached to a pillar, simply move the\r
407                                                 // resizer.\r
408                                                 if ( resizer && resizer.move( evt.$.clientX ) )\r
409                                                 {\r
410                                                         cancel( evt );\r
411                                                         return;\r
412                                                 }\r
413 \r
414                                                 // Considering table, tr, td, tbody but nothing else.\r
415                                                 var target = evt.getTarget(),\r
416                                                         table,\r
417                                                         pillars;\r
418 \r
419                                                 if ( !target.is( 'table' ) && !target.getAscendant( 'tbody', 1 ) )\r
420                                                         return;\r
421 \r
422                                                 table = target.getAscendant( 'table', 1 );\r
423 \r
424                                                 if ( !( pillars = table.getCustomData( '_cke_table_pillars' ) ) )\r
425                                                 {\r
426                                                         // Cache table pillars calculation result.\r
427                                                         table.setCustomData( '_cke_table_pillars', ( pillars = buildTableColumnPillars( table ) ) );\r
428                                                         table.on( 'mouseout', clearPillarsCache );\r
429                                                         table.on( 'mousedown', clearPillarsCache );\r
430                                                 }\r
431 \r
432                                                 var pillar = getPillarAtPosition( pillars, evt.$.clientX );\r
433                                                 if ( pillar )\r
434                                                 {\r
435                                                         !resizer && ( resizer = new columnResizer( editor ) );\r
436                                                         resizer.attachTo( pillar );\r
437                                                 }\r
438                                         });\r
439                         });\r
440                 }\r
441         });\r
442 \r
443 })();\r