JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.4b
[ckeditor.git] / _source / plugins / tableresize / plugin.js
diff --git a/_source/plugins/tableresize/plugin.js b/_source/plugins/tableresize/plugin.js
new file mode 100644 (file)
index 0000000..f16978a
--- /dev/null
@@ -0,0 +1,450 @@
+/*\r
+Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+For licensing, see LICENSE.html or http://ckeditor.com/license\r
+*/\r
+\r
+(function()\r
+{\r
+       var pxUnit = CKEDITOR.tools.cssLength,\r
+               needsIEHacks = CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.quirks || CKEDITOR.env.version < 7 );\r
+\r
+       function getWidth( el )\r
+       {\r
+               return CKEDITOR.env.ie ? el.$.clientWidth : parseInt( el.getComputedStyle( 'width' ), 10 );\r
+       }\r
+\r
+       function getBorderWidth( element, side )\r
+       {\r
+               var computed = element.getComputedStyle( 'border-' + side + '-width' ),\r
+                       borderMap =\r
+                       {\r
+                               thin: '0px',\r
+                               medium: '1px',\r
+                               thick: '2px'\r
+                       };\r
+\r
+               if ( computed.indexOf( 'px' ) < 0 )\r
+               {\r
+                       // look up keywords\r
+                       if ( computed in borderMap && element.getComputedStyle( 'border-style' ) != 'none' )\r
+                               computed = borderMap[ computed ];\r
+                       else\r
+                               computed = 0;\r
+               }\r
+\r
+               return parseInt( computed, 10 );\r
+       }\r
+\r
+       // Gets the table row that contains the most columns.\r
+       function getMasterPillarRow( table )\r
+       {\r
+               var $rows = table.$.rows,\r
+                       maxCells = 0, cellsCount,\r
+                       $elected, $tr;\r
+\r
+               for ( var i = 0, len = $rows.length ; i < len; i++ )\r
+               {\r
+                       $tr = $rows[ i ];\r
+                       cellsCount = $tr.cells.length;\r
+\r
+                       if ( cellsCount > maxCells )\r
+                       {\r
+                               maxCells = cellsCount;\r
+                               $elected = $tr;\r
+                       }\r
+               }\r
+\r
+               return $elected;\r
+       }\r
+\r
+       function buildTableColumnPillars( table )\r
+       {\r
+               var pillars = [],\r
+                       pillarIndex = -1,\r
+                       rtl = ( table.getComputedStyle( 'direction' ) == 'rtl' );\r
+\r
+               // Get the raw row element that cointains the most columns.\r
+               var $tr = getMasterPillarRow( table );\r
+\r
+               // Get the tbody element and position, which will be used to set the\r
+               // top and bottom boundaries.\r
+               var tbody = new CKEDITOR.dom.element( table.$.tBodies[ 0 ] ),\r
+                       tbodyPosition = tbody.getDocumentPosition();\r
+\r
+               // Loop thorugh all cells, building pillars after each one of them.\r
+               for ( var i = 0, len = $tr.cells.length ; i < len ; i++ )\r
+               {\r
+                       // Both the current cell and the successive one will be used in the\r
+                       // pillar size calculation.\r
+                       var td = new CKEDITOR.dom.element( $tr.cells[ i ] ),\r
+                               nextTd = $tr.cells[ i + 1 ] && new CKEDITOR.dom.element( $tr.cells[ i + 1 ] );\r
+\r
+                       pillarIndex += td.$.colSpan || 1;\r
+\r
+                       // Calculate the pillar boundary positions.\r
+                       var pillarLeft, pillarRight, pillarWidth, pillarPadding;\r
+\r
+                       var x = td.getDocumentPosition().x;\r
+\r
+                       // Calculate positions based on the current cell.\r
+                       rtl ?\r
+                               pillarRight = x + getBorderWidth( td, 'left' ) :\r
+                               pillarLeft  = x + td.$.offsetWidth - getBorderWidth( td, 'right' );\r
+\r
+                       // Calculate positions based on the next cell, if available.\r
+                       if ( nextTd )\r
+                       {\r
+                               x =  nextTd.getDocumentPosition().x;\r
+\r
+                               rtl ?\r
+                                       pillarLeft      = x + nextTd.$.offsetWidth - getBorderWidth( nextTd, 'right' ) :\r
+                                       pillarRight     = x + getBorderWidth( nextTd, 'left' );\r
+                       }\r
+                       // Otherwise calculate positions based on the table (for last cell).\r
+                       else\r
+                       {\r
+                               x =  table.getDocumentPosition().x;\r
+\r
+                               rtl ?\r
+                                       pillarLeft      = x :\r
+                                       pillarRight     = x + table.$.offsetWidth;\r
+                       }\r
+\r
+                       pillarWidth = Math.max( pillarRight - pillarLeft, 3 );\r
+\r
+                       // Make the pillar touch area at least 14 pixels wide, for easy to use.\r
+                       pillarPadding = Math.max( Math.round( 7 - ( pillarWidth / 2 ) ), 0 );\r
+\r
+                       // The pillar should reflects exactly the shape of the hovered\r
+                       // column border line.\r
+                       pillars.push( {\r
+                               table : table,\r
+                               index : pillarIndex,\r
+                               x : pillarLeft,\r
+                               y : tbodyPosition.y,\r
+                               width : pillarWidth,\r
+                               height: tbody.$.offsetHeight,\r
+                               padding : pillarPadding,\r
+                               rtl : rtl } );\r
+               }\r
+\r
+               return pillars;\r
+       }\r
+\r
+       function getPillarAtPosition( pillars, positionX )\r
+       {\r
+               for ( var i = 0, len = pillars.length ; i < len ; i++ )\r
+               {\r
+                       var pillar = pillars[ i ],\r
+                               pad = pillar.padding;\r
+\r
+                       if ( positionX >= pillar.x - pad && positionX <= ( pillar.x + pillar.width + pad ) )\r
+                               return pillar;\r
+               }\r
+\r
+               return null;\r
+       }\r
+\r
+       function cancel( evt )\r
+       {\r
+               ( evt.data || evt ).preventDefault();\r
+       }\r
+\r
+       function columnResizer( editor )\r
+       {\r
+               var pillar,\r
+                       document,\r
+                       resizer,\r
+                       isResizing,\r
+                       startOffset,\r
+                       currentShift;\r
+\r
+               var leftSideCells, rightSideCells, leftShiftBoundary, rightShiftBoundary;\r
+\r
+               function detach()\r
+               {\r
+                       pillar = null;\r
+                       currentShift = 0;\r
+                       isResizing = 0;\r
+\r
+                       document.removeListener( 'mouseup', onMouseUp );\r
+                       resizer.removeListener( 'mousedown', onMouseDown );\r
+                       resizer.removeListener( 'mousemove', onMouseMove );\r
+\r
+                       document.getBody().setStyle( 'cursor', 'auto' );\r
+\r
+                       // Hide the resizer (remove it on IE7 - #5890).\r
+                       needsIEHacks ? resizer.remove() : resizer.hide();\r
+               }\r
+\r
+               function resizeStart()\r
+               {\r
+                       // Before starting to resize, figure out which cells to change\r
+                       // and the boundaries of this resizing shift.\r
+\r
+                       var columnIndex = pillar.index,\r
+                               map = CKEDITOR.tools.buildTableMap( pillar.table ),\r
+                               leftColumnCells = [],\r
+                               rightColumnCells = [],\r
+                               leftMinSize = Number.MAX_VALUE,\r
+                               rightMinSize = leftMinSize,\r
+                               rtl = pillar.rtl;\r
+\r
+                       for ( var i = 0, len = map.length ; i < len ; i++ )\r
+                       {\r
+                               var row                 = map[ i ],\r
+                                       leftCell        = row[ columnIndex + ( rtl ? 1 : 0 ) ],\r
+                                       rightCell       = row[ columnIndex + ( rtl ? 0 : 1 ) ];\r
+\r
+                               leftCell        = leftCell && new CKEDITOR.dom.element( leftCell );\r
+                               rightCell       = rightCell && new CKEDITOR.dom.element( rightCell );\r
+\r
+                               if ( !leftCell || !rightCell || !leftCell.equals( rightCell ) )\r
+                               {\r
+                                       leftCell && ( leftMinSize = Math.min( leftMinSize, getWidth( leftCell ) ) );\r
+                                       rightCell && ( rightMinSize = Math.min( rightMinSize, getWidth( rightCell ) ) );\r
+\r
+                                       leftColumnCells.push( leftCell );\r
+                                       rightColumnCells.push( rightCell );\r
+                               }\r
+                       }\r
+\r
+                       // Cache the list of cells to be resized.\r
+                       leftSideCells = leftColumnCells;\r
+                       rightSideCells = rightColumnCells;\r
+\r
+                       // Cache the resize limit boundaries.\r
+                       leftShiftBoundary =  pillar.x - leftMinSize;\r
+                       rightShiftBoundary = pillar.x + rightMinSize;\r
+\r
+                       resizer.setOpacity( 0.5 );\r
+                       startOffset = parseInt( resizer.getStyle( 'left' ), 10 );\r
+                       currentShift = 0;\r
+                       isResizing = 1;\r
+\r
+                       resizer.on( 'mousemove', onMouseMove );\r
+\r
+                       // Prevent the native drag behavior otherwise 'mousemove' won't fire.\r
+                       document.on( 'dragstart', cancel );\r
+               }\r
+\r
+               function resizeEnd()\r
+               {\r
+                       isResizing = 0;\r
+\r
+                       resizer.setOpacity( 0 );\r
+\r
+                       currentShift && resizeColumn();\r
+\r
+                       var table = pillar.table;\r
+                       setTimeout( function () { table.removeCustomData( '_cke_table_pillars' ); }, 0 );\r
+\r
+                       document.removeListener( 'dragstart', cancel );\r
+               }\r
+\r
+               function resizeColumn()\r
+               {\r
+                       var rtl = pillar.rtl,\r
+                               cellsCount = rtl ? rightSideCells.length : leftSideCells.length;\r
+\r
+                       // Perform the actual resize to table cells, only for those by side of the pillar.\r
+                       for ( var i = 0 ; i < cellsCount ; i++ )\r
+                       {\r
+                               var leftCell = leftSideCells[ i ],\r
+                                       rightCell = rightSideCells[ i ],\r
+                                       table = pillar.table;\r
+\r
+                               // Defer the resizing to avoid any interference among cells.\r
+                               CKEDITOR.tools.setTimeout(\r
+                                       function( leftCell, leftOldWidth, rightCell, rightOldWidth, tableWidth, sizeShift )\r
+                                       {\r
+                                               leftCell && leftCell.setStyle( 'width', pxUnit( Math.max( leftOldWidth + sizeShift, 0 ) ) );\r
+                                               rightCell && rightCell.setStyle( 'width', pxUnit( Math.max( rightOldWidth - sizeShift, 0 ) ) );\r
+\r
+                                               // If we're in the last cell, we need to resize the table as well\r
+                                               if ( tableWidth )\r
+                                                       table.setStyle( 'width', pxUnit( tableWidth + sizeShift * ( rtl ? -1 : 1 ) ) );\r
+                                       }\r
+                                       , 0,\r
+                                       this, [\r
+                                               leftCell, leftCell && getWidth( leftCell ),\r
+                                               rightCell, rightCell && getWidth( rightCell ),\r
+                                               ( !leftCell || !rightCell ) && ( getWidth( table ) + getBorderWidth( table, 'left' ) + getBorderWidth( table, 'right' ) ),\r
+                                               currentShift ] );\r
+                       }\r
+               }\r
+\r
+               function onMouseDown( evt )\r
+               {\r
+                       cancel( evt );\r
+\r
+                       resizeStart();\r
+\r
+                       document.on( 'mouseup', onMouseUp, this );\r
+               }\r
+\r
+               function onMouseUp( evt )\r
+               {\r
+                       evt.removeListener();\r
+\r
+                       resizeEnd();\r
+               }\r
+\r
+               function onMouseMove( evt )\r
+               {\r
+                       move( evt.data.$.clientX );\r
+               }\r
+\r
+               document = editor.document;\r
+\r
+               resizer = CKEDITOR.dom.element.createFromHtml(\r
+                       '<div cke_temp=1 contenteditable=false unselectable=on '+\r
+                       'style="position:absolute;cursor:col-resize;filter:alpha(opacity=0);opacity:0;' +\r
+                               'padding:0;background-color:#004;background-image:none;border:0px none;z-index:10"></div>', document );\r
+\r
+               // Except on IE6/7 (#5890), place the resizer after body to prevent it\r
+               // from being editable.\r
+               if ( !needsIEHacks )\r
+                       document.getDocumentElement().append( resizer );\r
+\r
+               this.attachTo = function( targetPillar )\r
+               {\r
+                       // Accept only one pillar at a time.\r
+                       if ( isResizing )\r
+                               return;\r
+\r
+                       // On IE6/7, we append the resizer everytime we need it. (#5890)\r
+                       if ( needsIEHacks )\r
+                       {\r
+                               document.getBody().append( resizer );\r
+                               currentShift = 0;\r
+                       }\r
+\r
+                       pillar = targetPillar;\r
+\r
+                       resizer.setStyles(\r
+                               {\r
+                                       width: pxUnit( targetPillar.width ),\r
+                                       height : pxUnit( targetPillar.height ),\r
+                                       left : pxUnit( targetPillar.x ),\r
+                                       top : pxUnit( targetPillar.y )\r
+                               });\r
+\r
+                       // In IE6/7, it's not possible to have custom cursors for floating\r
+                       // elements in an editable document. Show the resizer in that case,\r
+                       // to give the user a visual clue.\r
+                       needsIEHacks && resizer.setOpacity( 0.25 );\r
+\r
+                       resizer.on( 'mousedown', onMouseDown, this );\r
+\r
+                       document.getBody().setStyle( 'cursor', 'col-resize' );\r
+\r
+                       // Display the resizer to receive events but don't show it,\r
+                       // only change the cursor to resizable shape.\r
+                       resizer.show();\r
+               };\r
+\r
+               var move = this.move = function( posX )\r
+               {\r
+                       if ( !pillar )\r
+                               return 0;\r
+\r
+                       var pad = pillar.padding;\r
+\r
+                       if ( !isResizing && ( posX < pillar.x - pad || posX > ( pillar.x + pillar.width + pad ) ) )\r
+                       {\r
+                               detach();\r
+                               return 0;\r
+                       }\r
+\r
+                       var resizerNewPosition = posX - Math.round( resizer.$.offsetWidth / 2 );\r
+\r
+                       if ( isResizing )\r
+                       {\r
+                               if ( resizerNewPosition == leftShiftBoundary || resizerNewPosition == rightShiftBoundary )\r
+                                       return 1;\r
+\r
+                               resizerNewPosition = Math.max( resizerNewPosition, leftShiftBoundary );\r
+                               resizerNewPosition = Math.min( resizerNewPosition, rightShiftBoundary );\r
+\r
+                               currentShift = resizerNewPosition - startOffset;\r
+                       }\r
+\r
+                       resizer.setStyle( 'left', pxUnit( resizerNewPosition ) );\r
+\r
+                       return 1;\r
+               };\r
+       }\r
+\r
+       function clearPillarsCache( evt )\r
+       {\r
+               var target = evt.data.getTarget();\r
+\r
+               if ( evt.name == 'mouseout' )\r
+               {\r
+                       // Bypass interal mouse move.\r
+                       if ( !target.is ( 'table' ) )\r
+                               return;\r
+\r
+                       var dest = new CKEDITOR.dom.element( evt.data.$.relatedTarget || evt.data.$.toElement );\r
+                       while( dest && dest.$ && !dest.equals( target ) && !dest.is( 'body' ) )\r
+                               dest = dest.getParent();\r
+                       if ( !dest || dest.equals( target ) )\r
+                               return;\r
+               }\r
+\r
+               target.getAscendant( 'table', true ).removeCustomData( '_cke_table_pillars' );\r
+               evt.removeListener();\r
+       }\r
+\r
+       CKEDITOR.plugins.add( 'tableresize',\r
+       {\r
+               requires : [ 'tabletools' ],\r
+               init : function( editor )\r
+               {\r
+                       editor.on( 'contentDom', function()\r
+                       {\r
+                               var resizer;\r
+\r
+                               editor.document.getBody().on( 'mousemove', function( evt )\r
+                                       {\r
+                                               evt = evt.data;\r
+\r
+                                               // If we're already attached to a pillar, simply move the\r
+                                               // resizer.\r
+                                               if ( resizer && resizer.move( evt.$.clientX ) )\r
+                                               {\r
+                                                       cancel( evt );\r
+                                                       return;\r
+                                               }\r
+\r
+                                               // Considering table, tr, td, tbody but nothing else.\r
+                                               var target = evt.getTarget(),\r
+                                                       table,\r
+                                                       pillars;\r
+\r
+                                               if ( !target.is( 'table' ) && !target.getAscendant( 'tbody', true ) )\r
+                                                       return;\r
+\r
+                                               table = target.getAscendant( 'table', true );\r
+\r
+                                               if ( !( pillars = table.getCustomData( '_cke_table_pillars' ) ) )\r
+                                               {\r
+                                                       // Cache table pillars calculation result.\r
+                                                       table.setCustomData( '_cke_table_pillars', ( pillars = buildTableColumnPillars( table ) ) );\r
+                                                       table.on( 'mouseout', clearPillarsCache );\r
+                                                       table.on( 'mousedown', clearPillarsCache );\r
+                                               }\r
+\r
+                                               var pillar = getPillarAtPosition( pillars, evt.$.clientX );\r
+                                               if ( pillar )\r
+                                               {\r
+                                                       !resizer && ( resizer = new columnResizer( editor ) );\r
+                                                       resizer.attachTo( pillar );\r
+                                               }\r
+                                       });\r
+                       });\r
+               }\r
+       });\r
+\r
+})();\r