| 417 | | //Returns TRUE if any cell has width set |
| 418 | | FCKTableHandler.HasCellWidths = function( table ) |
| 419 | | { |
| 420 | | for ( var r = 0; r < table.rows.length; r++ ) |
| 421 | | { |
| 422 | | for ( var c = 0; c < table.rows[r].cells.length; c++ ) |
| 423 | | { |
| 424 | | if ( table.rows[r].cells[c].width ) return true; |
| 425 | | } |
| 426 | | } |
| 427 | | return false; |
| 428 | | } |
| 429 | | |
| 430 | | //Clears all cell widths/heights ( letting the browser size everything ) |
| 431 | | FCKTableHandler.ClearCellWidths = function( table ) |
| 432 | | { |
| 433 | | for ( var r = 0; r < table.rows.length; r++ ) |
| 434 | | { |
| 435 | | for ( var c = 0; c < table.rows[r].cells.length; c++ ) |
| 436 | | { |
| 437 | | table.rows[r].cells[c].width = ""; |
| 438 | | table.rows[r].cells[c].height = ""; |
| 439 | | table.rows[r].cells[c].removeAttribute( "WIDTH" ); |
| 440 | | table.rows[r].cells[c].removeAttribute( "HEIGHT" ); |
| 441 | | } |
| 442 | | } |
| 443 | | } |
| 444 | | |
| 445 | | //Cleanup table widths. If bPercent, recalculates table widths in %. If bClear, |
| 446 | | //moves all table widths as early as possible |
| 447 | | |
| 448 | | FCKTableHandler.CleanupTableWidths = function( table, bPercent, bClear, aMapIn ) |
| 449 | | { |
| 450 | | var aMap = aMapIn ? aMapIn : this._CreateTableMap( table ) ; |
| 451 | | var nCols = aMap[0].length; |
| 452 | | var nRows = aMap.length; |
| 453 | | var nTotalWidth = 0; |
| 454 | | var aSizeCells = new Array(); |
| 455 | | var aSizes = new Array(); |
| 456 | | //work the table column-by-column |
| 457 | | for ( var nCol = 0; nCol < nCols; nCol++ ) |
| 458 | | { |
| 459 | | var nMax = 0; |
| 460 | | var oCell = null; |
| 461 | | //walk the rows for a specific column -- find the topmost non-colspan'd item |
| 462 | | //and the maximum pixel width ( according to the browser ) |
| 463 | | for ( var nRow = 0; nRow < nRows; nRow++ ) |
| 464 | | { |
| 465 | | if ( aMap[nRow][nCol].colSpan > 1 ) continue; |
| 466 | | //use topmost non-colspan'd cell to put the width in |
| 467 | | if ( !oCell ) oCell = aMap[nRow][nCol]; |
| 468 | | if ( aMap[nRow][nCol].offsetWidth > nMax ) nMax = aMap[nRow][nCol].offsetWidth; |
| 469 | | } |
| 470 | | aSizes[nCol] = nMax; |
| 471 | | nTotalWidth += nMax; |
| 472 | | aSizeCells[nCol] = oCell; |
| 473 | | } |
| 474 | | //if we were asked for percentage width, go through and convert the sizes to %. note |
| 475 | | //that we do this based on the calculated width of all the cells; this is more |
| 476 | | //reliable because offsetWidth, et al., includes table borders, padding, etc. |
| 477 | | for ( var nCol = 0; nCol < aSizeCells.length; nCol++ ) |
| 478 | | { |
| 479 | | if ( bPercent ) |
| 480 | | { |
| 481 | | var nWidth = Math.round( 100 * (aSizeCells[nCol].offsetWidth/nTotalWidth )); |
| 482 | | if ( nWidth == 0 ) nWidth = 1; |
| 483 | | strWidth = nWidth + "%"; |
| 484 | | } |
| 485 | | else |
| 486 | | { |
| 487 | | strWidth = aSizes[nCol]; |
| 488 | | } |
| 489 | | |
| 490 | | //set all width attributes |
| 491 | | for ( var nRow = 0; nRow < nRows; nRow++ ) |
| 492 | | { |
| 493 | | if ( aMap[nRow][nCol].colSpan == 1 ) |
| 494 | | { |
| 495 | | aMap[nRow][nCol].width = bClear ? '' : strWidth; |
| 496 | | if ( bClear ) aMap[nRow][nCol].removeAttribute( "width" ); |
| 497 | | aSizeCells[nCol].width = strWidth; |
| 498 | | } |
| 499 | | } |
| 500 | | } |
| 501 | | } |
| 502 | | |
| 503 | | // Move the cursor into the next ( or previous ) cell |
| 504 | | FCKTableHandler.MoveToNextCell = function( bRev ) |
| 505 | | { |
| 506 | | var aCells = FCKTableHandler.GetSelectedCells(); |
| 507 | | if ( aCells.length == 0 ) return; |
| 508 | | var oCell = aCells[0]; |
| 509 | | var oNextCell = bRev ? oCell.previousSibling: oCell.nextSibling; |
| 510 | | if ( !oNextCell ) |
| 511 | | { |
| 512 | | var oNext; |
| 513 | | var oRow = FCKTools.GetElementAscensor( oCell,"TR" ); |
| 514 | | if ( bRev ) |
| 515 | | { |
| 516 | | if ( !(oNext = oRow.previousSibling )) return; |
| 517 | | oNextCell=oNext.cells[oNext.cells.length - 1]; |
| 518 | | } |
| 519 | | else |
| 520 | | { |
| 521 | | if ( !(oNext = oRow.nextSibling )) return; |
| 522 | | oNextCell=oNext.cells[0]; |
| 523 | | } |
| 524 | | } |
| 525 | | FCKSelection.SelectNode( oNextCell ); |
| 526 | | FCKSelection.Collapse(); |
| 527 | | } |
| 528 | | ////////////////////////////////////////////////////////////////// |
| 529 | | // Table Sizer |
| 530 | | // |
| 531 | | |
| 532 | | |
| 533 | | // Returns the closest sizeable cell to point (or the left of the specified point |
| 534 | | // if between cells). Generally, this will be a heading cell (i.e. a cell in |
| 535 | | // the first row). If *all* the candidate cells are colspan'd (heaven forbid), |
| 536 | | // don't allow sizing. x and y are in client coordinates |
| 537 | | |
| 538 | | FCKTableHandler.SizableCellFromPoint = function( table, ncx, ncy ) |
| 539 | | { |
| 540 | | var aRows = table.rows ; |
| 541 | | var x = ncx - this._CalcPosition( table )[0]; |
| 542 | | for ( var i = 0 ; i < aRows.length ; i++ ) |
| 543 | | { |
| 544 | | for ( var j = 0; j < aRows[i].cells.length - 1; j++ ) |
| 545 | | { |
| 546 | | if ( aRows[i].cells[j + 1].offsetLeft > x ) |
| 547 | | { |
| 548 | | if ( aRows[i].cells[j].colSpan < 2 ) |
| 549 | | { |
| 550 | | return aRows[i].cells[j]; |
| 551 | | } |
| 552 | | else |
| 553 | | { |
| 554 | | break; |
| 555 | | } |
| 556 | | } |
| 557 | | } |
| 558 | | } |
| 559 | | return null; |
| 560 | | } |
| 561 | | |
| 562 | | // Creates the table resize bar. If a table resize bar is present, uses it. You |
| 563 | | // must explicitly destroy the table resize bar with DestroyResizeBar after use. |
| 564 | | FCKTableHandler.CreateResizeBar = function() |
| 565 | | { |
| 566 | | if ( this.ResizeBar ) return this.ResizeBar; |
| 567 | | var oBar; |
| 568 | | oBar = FCK.EditorDocument.createElement( "SPAN" ); |
| 569 | | |
| 570 | | // setup the bar |
| 571 | | oBar.id = '__FCKResizeBar'; |
| 572 | | oBar.style.position = "absolute"; |
| 573 | | oBar.style.top = "0px"; |
| 574 | | oBar.style.left = "0px"; |
| 575 | | oBar.style.height = "0px"; |
| 576 | | oBar.style.width = "2px"; |
| 577 | | oBar.style.borderLeft = "1px solid #0033FF"; |
| 578 | | oBar.style.display = "none"; |
| 579 | | oBar.style.cursor = "e-resize"; |
| 580 | | |
| 581 | | FCK.EditorDocument.body.appendChild( oBar ); |
| 582 | | this.ResizeBar = oBar; |
| 583 | | return oBar; |
| 584 | | } |
| 585 | | |
| 586 | | // Destroys the table resize bar |
| 587 | | FCKTableHandler.DestroyResizeBar = function() |
| 588 | | { |
| 589 | | if ( !this.ResizeBar ) return; |
| 590 | | this.ResizeBar.parentNode.removeChild( FCKTableHandler.ResizeBar ); |
| 591 | | this.ResizeBar = null; |
| 592 | | } |
| 593 | | |
| 594 | | // Figures out whether we want to size or let the browser do its thing. We can |
| 595 | | // size if the table is the target and we're between cells, OR if we're on |
| 596 | | // a table cell and on the far right edge of the cell within 3px of the edge |
| 597 | | // ( allows us to easily size tables with thin borders ) |
| 598 | | |
| 599 | | FCKTableHandler.CanSizeHere = function( ev, cell, tbl ) |
| 600 | | { |
| 601 | | var elem = ev.srcElement || ev.originalTarget; |
| 602 | | var aCellLoc = this._CalcPosition( cell ); |
| 603 | | var aTableLoc = this._CalcPosition( tbl ); |
| 604 | | //in the td client area, but not on the edge |
| 605 | | if ( elem != tbl ) return ( (aCellLoc[0] + cell.offsetWidth - ev.clientX ) < 3); |
| 606 | | //on the table edge |
| 607 | | if ( (aTableLoc[0] + tbl.offsetWidth - ev.clientX ) < 3 || |
| 608 | | ( aTableLoc[0] - ev.clientX ) < 3 || |
| 609 | | ( aTableLoc[1] + tbl.offsetHeight - ev.clientY < 3 ) || |
| 610 | | ( aTableLoc[1] - ev.clientY < 3 )) return false; |
| 611 | | return true; |
| 612 | | } |
| 613 | | |
| 614 | | |
| 615 | | // Sets up table sizing |
| 616 | | FCKTableHandler.StartTableSizing = function( ev, cell, tbl ) |
| 617 | | { |
| 618 | | if ( !cell ) return null; |
| 619 | | this.tableMap = this._CreateTableMap( tbl ) ; |
| 620 | | var aCellLoc = this._GetCellLocation( this.tableMap, cell ); //returns [row,col] |
| 621 | | if ( !aCellLoc ) return null; |
| 622 | | //we're in the last column, so we can't size |
| 623 | | if ( aCellLoc[1] == this.tableMap[aCellLoc[0]].length - 1 ) return null; |
| 624 | | var nTarget = aCellLoc[1] + 1; |
| 625 | | this.adjacentCell = null; |
| 626 | | for ( var i=0; i < this.tableMap.length; i++ ) |
| 627 | | { |
| 628 | | if ( this.tableMap[i][nTarget].colSpan < 2 ) |
| 629 | | { |
| 630 | | this.adjacentCell = this.tableMap[i][nTarget]; |
| 631 | | break; |
| 632 | | } |
| 633 | | } |
| 634 | | if ( this.adjacentCell == null ) return null; //can't size, no adjacent col |
| 635 | | this.targetCell = cell; |
| 636 | | //ok. we have everything we need. off we go. |
| 637 | | |
| 638 | | //create the resize bar |
| 639 | | this.pixWidths = !( this.targetCell.width.search('%' ) == -1 || !this.targetCell.width); |
| 640 | | var oVBar = this.CreateResizeBar(); |
| 641 | | //oVBar.style.left = FCK.EditorDocument.body.scrollLeft + this._CalcPosition( cell ) + cell.offsetWidth; |
| 642 | | //this.origPos = FCK.EditorDocument.body.scrollLeft + ev.clientX; |
| 643 | | this.origPos = FCK.EditorDocument.body.scrollLeft + this._CalcPosition( cell )[0] + cell.offsetWidth; |
| 644 | | oVBar.style.left = this.origPos; |
| 645 | | oVBar.style.cursor = "e-resize"; |
| 646 | | oVBar.setAttribute( "UNSELECTABLE","on" ); |
| 647 | | tbl.setAttribute( "UNSELECTABLE","on" ); |
| 648 | | this.sizeOffset = ev.clientX - this.origPos; |
| 649 | | //minimum size is always the left edge of the cell + some safety margin. |
| 650 | | //max size is always the right edge of the next cell - some safety margin |
| 651 | | this.minSize = FCK.EditorDocument.body.scrollLeft + this._CalcPosition( cell )[0] + 10; |
| 652 | | this.maxSize = FCK.EditorDocument.body.scrollLeft + this._CalcPosition( this.adjacentCell )[0] + this.adjacentCell.offsetWidth - 10; |
| 653 | | oVBar.style.top = this._CalcPosition( tbl )[1]; |
| 654 | | oVBar.style.height = tbl.clientHeight + ( tbl.border*2 ); |
| 655 | | oVBar.style.display = "inline"; |
| 656 | | this.CleanupTableWidths( tbl,false, true,this.tableMap ); |
| 657 | | return true; |
| 658 | | } |
| 659 | | |
| 660 | | FCKTableHandler.FinishTableSizing = function() |
| 661 | | { |
| 662 | | var tbl = FCKTools.GetElementAscensor( this.targetCell,"TABLE" ); |
| 663 | | var nTargetDiff = this.ResizeBar.offsetLeft - this.origPos; |
| 664 | | //widths are in pixels after StartTableSizing, so this is safe |
| 665 | | tbl.style.cursor = ''; |
| 666 | | if ( tbl.runtimeStyle ) tbl.runtimeStyle.cursor = ''; |
| 667 | | this.targetCell.width = parseInt( this.targetCell.width ) + nTargetDiff; |
| 668 | | this.adjacentCell.width = parseInt( this.adjacentCell.width ) - nTargetDiff; |
| 669 | | this.DestroyResizeBar(); |
| 670 | | this.CleanupTableWidths( tbl,this.pixWidths,true,this.tableMap ); |
| 671 | | this.targetCell = null; |
| 672 | | this.origPos = null; |
| 673 | | tbl.removeAttribute( "UNSELECTABLE" ); |
| 674 | | if ( FCKBrowserInfo.IsGecko ) tbl.style.MozUserSelect = ""; |
| 675 | | } |
| 676 | | |
| 677 | | // Sizes the table to the given mouse position |
| 678 | | FCKTableHandler.SizeTo = function( x ) |
| 679 | | { |
| 680 | | var nPos = x - this.sizeOffset; |
| 681 | | if ( nPos > this.maxSize ) nPos = this.maxSize; |
| 682 | | if ( nPos < this.minSize ) nPos = this.minSize; |
| 683 | | //runtimeStyle is way faster under IE |
| 684 | | if ( this.ResizeBar.runtimeStyle ) |
| 685 | | this.ResizeBar.runtimeStyle.left = nPos; |
| 686 | | else |
| 687 | | this.ResizeBar.style.left = nPos; |
| 688 | | if ( FCKBrowserInfo.IsIE && FCK.EditorDocument.selection.type != 'None' ) |
| 689 | | FCK.EditorDocument.selection.collapse(); |
| 690 | | } |
| 691 | | |
| 692 | | // returns TRUE if we are actively sizing a table |
| 693 | | FCKTableHandler.IsTableSizing = function() |
| 694 | | { |
| 695 | | return ( this.targetCell != null ); |
| 696 | | } |
| 697 | | |
| 698 | | // figure out where ( with respect to the edge of the parent ) a given table/cell is |
| 699 | | FCKTableHandler._CalcPosition = function( ob ) |
| 700 | | { |
| 701 | | var oParent=ob; |
| 702 | | var nY = 0; |
| 703 | | var nX = 0; |
| 704 | | while ( oParent && oParent.tagName != "BODY" ) |
| 705 | | { |
| 706 | | nX += oParent.offsetLeft; |
| 707 | | nY += oParent.offsetTop; |
| 708 | | oParent = oParent.offsetParent; |
| 709 | | } |
| 710 | | return [nX,nY]; |
| 711 | | } |
| 712 | | |
| 713 | | FCKTableHandler.tbl_MouseDown = function ( FCK, e ) |
| 714 | | { |
| 715 | | var elem = e.srcElement || e.originalTarget; |
| 716 | | var strTag = elem ? elem.tagName.toUpperCase() : null; |
| 717 | | //find the table we're part of, if we are |
| 718 | | if ( elem && (strTag == 'TABLE' || strTag == 'TD' || strTag == 'TH' ) ) |
| 719 | | { |
| 720 | | FCKTableHandler.targetCell = null; |
| 721 | | var oTable = FCKTools.GetElementAscensor( elem, "TABLE" ); |
| 722 | | //figure out if there's a cell we can size |
| 723 | | var oCell = FCKTableHandler.SizableCellFromPoint( oTable, e.clientX, e.clientY ); |
| 724 | | if ( !oCell ) return true; |
| 725 | | if ( FCKTableHandler.CanSizeHere( e,oCell,oTable ) ) |
| 726 | | { |
| 727 | | if ( FCKTableHandler.StartTableSizing( e,oCell, oTable ) ) |
| 728 | | { |
| 729 | | if ( oTable.runtimeStyle ) oTable.runtimeStyle.cursor = "e-resize"; |
| 730 | | if ( FCKBrowserInfo.IsGecko ) oTable.style.MozUserSelect = "none"; |
| 731 | | e.cancelBubble = true ; |
| 732 | | e.returnValue = false ; |
| 733 | | return false; |
| 734 | | } |
| 735 | | } |
| 736 | | } |
| 737 | | return true; |
| 738 | | } |
| 739 | | |
| 740 | | FCKTableHandler.tbl_MouseUp = function ( FCK, e ) |
| 741 | | { |
| 742 | | if ( FCKTableHandler.IsTableSizing( )) FCKTableHandler.FinishTableSizing(); |
| 743 | | } |
| 744 | | |
| 745 | | FCKTableHandler.tbl_MouseMove = function ( FCK, e ) |
| 746 | | { |
| 747 | | var elem = e.srcElement || e.originalTarget; |
| 748 | | if ( FCKTableHandler.IsTableSizing() ) |
| 749 | | { |
| 750 | | FCKTableHandler.SizeTo( e.clientX ); |
| 751 | | e.cancelBubble = true ; |
| 752 | | e.returnValue = false ; |
| 753 | | return false; |
| 754 | | } |
| 755 | | |
| 756 | | if ( elem.tagName.toUpperCase() == 'TABLE' ) |
| 757 | | { |
| 758 | | //let the browser handle global sizing and move if the user is on |
| 759 | | //the RH edge of a table |
| 760 | | if ( elem.clientWidth - e.offsetX < 3 || |
| 761 | | elem.clientHeight - e.offsetY < 3 || |
| 762 | | e.offsetX < 2 || e.offsetY < 2 ) |
| 763 | | { |
| 764 | | elem.style.cursor = ""; |
| 765 | | return true; |
| 766 | | } |
| 767 | | //block browser sizing -- we're going to do it ourselves, thanks |
| 768 | | if ( elem.runtimeStyle ) elem.runtimeStyle.cursor = "e-resize"; |
| 769 | | e.cancelBubble = true ; |
| 770 | | e.returnValue = false ; |
| 771 | | return false; |
| 772 | | } |
| 773 | | else if ( elem.tagName.toUpperCase() == 'TD' ) |
| 774 | | { |
| 775 | | var tbl = FCKTools.GetElementAscensor( elem, "TABLE" ); |
| 776 | | if ( e.offsetX >= (elem.offsetWidth - 4 )) |
| 777 | | { |
| 778 | | e.cancelBubble = true ; |
| 779 | | e.returnValue = false ; |
| 780 | | return false; |
| 781 | | } |
| 782 | | } |
| 783 | | return true; |
| 784 | | } |
| 785 | | |
| 786 | | FCK.Events.AttachEvent( "OnMouseMove", FCKTableHandler.tbl_MouseMove ); |
| 787 | | FCK.Events.AttachEvent( "OnMouseDown", FCKTableHandler.tbl_MouseDown ); |
| 788 | | FCK.Events.AttachEvent( "OnMouseUp", FCKTableHandler.tbl_MouseUp ); |
| 789 | | |