/** * Aloha Table Plugin * ------------------ * This plugin provides advanced support for manipulating tables in Aloha * Editables. * Nested tables are not support. If nested tables are pasted into the * editable, they will simply be left alone. * Each (non-nested) table in the editable will have a corresponding Aloha * Table instance created for it, which will maintain internal state, and * information related to its DOM element. * * @todo: - selectRow/selectColumn should take into account the helper row/column. * ie: selectRow(0) and selectColumn(0), should be zero indexed */ define([ 'aloha', 'jquery', 'ui/scopes', 'ui/dialog', 'i18n!table/nls/i18n', 'table/table-cell', 'table/table-selection', 'table/table-plugin-utils' ], function ( Aloha, jQuery, Scopes, Dialog, i18n, TableCell, TableSelection, Utils ) { var undefined = void 0; var GENTICS = window.GENTICS; /** * Constructor of the table object * * @param table * the dom-representation of the held table * @return void */ var Table = function ( table, tablePlugin ) { // set the table attribut "obj" as a jquery represenation of the dom-table this.obj = jQuery( table ); correctTableStructure( this ); if ( !this.obj.attr( 'id' ) ) { this.obj.attr( 'id', GENTICS.Utils.guid() ); } this.tablePlugin = tablePlugin; this.selection = new TableSelection( this ); this.refresh(); }; jQuery.extend( Table.prototype, { /** * Attribute holding the jQuery-table-represenation */ obj: undefined, /** * The DOM-element of the outest div-container wrapped around the cell */ tableWrapper: undefined, /** * An array of all Cells contained in the Table * * @see TableCell */ cells: undefined, /** * Number of rows of the table */ numRows: undefined, /** * Number of rows of the table */ numCols: undefined, /** * Flag wether the table is active or not */ isActive: false, /** * Flag wether the table is focused or not */ hasFocus: false, /** * The editable which contains the table */ parentEditable: undefined, /** * Flag to check if the mouse was pressed. For row- and column-selection. */ mousedown: false, /** * ID of the column which was pressed when selecting columns */ clickedColumnId: -1, /** * ID of the row which was pressed when selecting rows */ clickedRowId: -1, /** * collection of columnindexes of the columns which should be selected */ columnsToSelect: [], /** * collection of rowindexes of the rows which should be selected */ rowsToSelect: [], /** * contains the plugin id used for interaction with the floating menu */ fmPluginId: undefined } ); /** * @hide */ Table.prototype.refresh = function () { // find the dimensions of the table this.numCols = this.countVirtualCols(); var rows = this.getRows(); this.numRows = rows.length; // init the cell-attribute with an empty array this.cells = []; // iterate over table cells and create Cell-objects for ( var i = 0; i < rows.length; i++ ) { var row = jQuery(rows[i]); var cols = row.children(); for ( var j = 0; j < cols.length; j++ ) { var col = cols[j]; var Cell = this.newCell( col ); } } }; Table.prototype.countVirtualCols = function () { var $firstRow = this.obj.children().children( 'tr:first-child' ).children(); return $firstRow.length - $firstRow.filter( '.' + this.get( 'classLeftUpperCorner' ) ).length; }; /** * Wrapper-Mehotd to return a property of TablePlugin.get * * @see TablePlugin.get * @param property * the property whichs value should be return * @return the value associated with the property */ Table.prototype.get = function(property) { return this.tablePlugin.get(property); }; /** * Wrapper-Method for TablePlugin.set * * @see TablePlugin.set * @param key * the key whichs value should be set * @param value * the value for the key * @return void */ Table.prototype.set = function(key, value) { this.tablePlugin.set(key, value); }; /** * Given an unbalanced table structure, pad it with the necessary cells to * make it perfectly rectangular * * @param {Aloha.Table} tableObj */ function correctTableStructure ( tableObj ) { var table = tableObj.obj, i, row, rows = tableObj.getRows(), rowsNum = rows.length, cols, colsNum, colsCount, maxColsCount = 0, cachedColsCounts = [], colsCountDiff, colSpan; for ( i = 0; i < rowsNum; i++ ) { row = jQuery( rows[ i ] ); cols = row.children( 'td, th' ); colsNum = cols.length; colsCount = Utils.cellIndexToGridColumn( rows, i, colsNum - 1 ) + 1; // Check if the last cell in this row has a col span, to account // for it in the total number of colums in this row colSpan = parseInt( cols.last().attr( 'colspan' ), 10 ); if ( colSpan == 0 ) { // TODO: support colspan=0 // http://dev.w3.org/html5/markup/td.html#td.attrs.colspan // http://www.w3.org/TR/html401/struct/tables.html#adef-colspan // The value zero ("0") means that the cell spans all columns // from the current column to the last column of the column // group (COLGROUP) in which the cel } else if ( !isNaN( colSpan ) ) { // The default value of this attribute is one ("1"), so where this // is the case, we will remove such superfluous colspan attributes if ( colSpan == 1 ) { cols.last().removeAttr( 'colspan' ); } colsCount += ( colSpan - 1 ); } cachedColsCounts.push( colsCount ); if ( colsCount > maxColsCount ) { maxColsCount = colsCount; } } for ( i = 0; i < rowsNum; i++ ) { colsCountDiff = maxColsCount - cachedColsCounts[ i ]; if ( colsCountDiff > 0 ) { // Create as many td's as we need to complete the row jQuery( rows[ i ] ).append( ( new Array( colsCountDiff + 1 ) ).join( '' ) ); } } }; /** * Transforms the existing dom-table into an editable aloha-table. In fact it * replaces the td-elements with equivalent TableCell-elements * with attached events. * Furthermore it creates wrapping divs to realize a click-area for row- and * column selection and also attaches events. * * @return void */ Table.prototype.activate = function () { if ( this.isActive ) { return; } var that = this, htmlTableWrapper, tableWrapper, eventContainer; // alter the table attributes this.obj.addClass( this.get( 'className' ) ); this.obj.contentEditable( false ); // set an id to the table if not already set if ( this.obj.attr( 'id' ) == '' ) { this.obj.attr( 'id', GENTICS.Utils.guid() ); } // unset the selection type this.selection.selectionType = undefined; // the eventContainer will be the tbody (if there is one), or the table (if no tbody exists) eventContainer = this.obj.children('tbody'); if (eventContainer.length === 0) { eventContainer = this.obj; } eventContainer.bind( 'keydown', function ( jqEvent ) { if ( !jqEvent.ctrlKey && !jqEvent.shiftKey ) { if ( that.selection.selectedCells.length > 0 && that.selection.selectedCells[ 0 ].length > 0 ) { that.selection.selectedCells[ 0 ][ 0 ].firstChild.focus(); } } } ); /* We need to make sure that when the user has selected text inside a table cell we do not delete the entire row, before we activate this this.obj.bind( 'keyup', function ( $event ) { if ( $event.keyCode == 46 ) { if ( that.selection.selectedColumnIdxs.length ) { that.deleteColumns(); $event.stopPropagation(); } else if ( that.selection.selectedRowIdxs.length ) { that.deleteRows(); $event.stopPropagation(); } else { // Nothing to delete } } } ); */ // handle click event of the table // this.obj.bind('click', function(e){ // // stop bubbling the event to the outer divs, a click in the table // // should only be handled in the table // e.stopPropagation(); // return false; // }); // // handle column/row resize eventContainer.delegate( 'td', 'mousemove', function( e ) { var jqObj = jQuery( this ); // offset to be used for activating the resize cursor near a table border var mouseOffset = 3; // filter out the control cells if ( jQuery( this ).hasClass( 'aloha-table-selectrow' ) || jQuery( this ).closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' )) return; var closeToLeftBorder = function(cell) { return ( ( e.pageX - cell.offset().left ) < mouseOffset ); }; var closeToTopBorder = function(cell) { return ( ( e.pageY - cell.offset().top ) < mouseOffset ); }; var closeToTableBottom = function(cell) { var row = cell.closest( 'tr'); // check if it's the last row if ( row.next( 'tr').length > 0 ) { return false } var cursorOffset = e.pageY - ( row.offset().top + row.outerHeight() ); return cursorOffset > (mouseOffset * -1) && cursorOffset < mouseOffset; } var colResize = that.tablePlugin.colResize; var rowResize = that.tablePlugin.rowResize; if ( colResize && closeToLeftBorder( jqObj ) ) { jqObj.css( 'cursor', 'col-resize' ); return that.attachColumnResize( jqObj ); } else if ( rowResize && closeToTopBorder( jqObj ) ) { jqObj.css( 'cursor', 'row-resize' ); return that.attachRowResize( jqObj ); } else if ( rowResize && closeToTableBottom( jqObj ) ) { jqObj.css( 'cursor', 'row-resize' ); return that.attachRowResize( jqObj, true ); } else { jqObj.css( 'cursor', 'default' ); return that.detachRowColResize( jqObj ); } }); eventContainer.bind( 'mousemove', function( e ) { var jqObj = jQuery( this ).closest( 'table' ); var isTableRightBorder = function( table ) { var cursorOffset = e.pageX - ( table.offset().left + table.outerWidth() ); return cursorOffset > -5 && cursorOffset < 5; }; var tableResize = that.tablePlugin.tableResize; if ( tableResize && isTableRightBorder( jqObj ) ) { return that.attachTableResizeWidth( jqObj ); } }); eventContainer.bind( 'mousedown', function ( jqEvent ) { // focus the table if not already done if ( !that.hasFocus ) { that.focus(); } // DEACTIVATED by Haymo prevents selecting rows // // if a mousedown is done on the table, just focus the first cell of the table // setTimeout(function() { // var firstCell = that.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0); // TableSelection.unselectCells(); // jQuery(firstCell).get(0).focus(); // // move focus in first cell // that.obj.cells[0].wrapper.get(0).focus(); // }, 0); // stop bubbling and default-behaviour jqEvent.stopPropagation(); jqEvent.preventDefault(); return false; } ); // ### create a wrapper for the table (@see HINT below) // wrapping div for the table to suppress the display of the resize-controls of // the editable divs within the cells // tha data-block-skip-scope attribute will keep the block plugin from setting the // FloatingMenu's scope when the block is clicked tableWrapper = jQuery( '
' ); //tableWrapper.contentEditable( false ); // wrap the tableWrapper around the table this.obj.wrap( tableWrapper ); // Check because the aloha block plugin may not be loaded var parent = this.obj.parent(); if (parent.alohaBlock) { parent.alohaBlock(); } // :HINT The outest div (Editable) of the table is still in an editable // div. So IE will surround the the wrapper div with a resize-border // Workaround => just disable the handles so hopefully won't happen any ugly stuff. // Disable resize and selection of the controls (only IE) // Events only can be set to elements which are loaded from the DOM (if they // were created dynamically before) ;) htmlTableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) ); htmlTableWrapper.get( 0 ).onresizestart = function ( e ) { return false; }; htmlTableWrapper.get( 0 ).oncontrolselect = function ( e ) { return false; }; htmlTableWrapper.get( 0 ).ondragstart = function ( e ) { return false; }; htmlTableWrapper.get( 0 ).onmovestart = function ( e ) { return false; }; // the following handler prevents proper selection in the editable div in the caption! // htmlTableWrapper.get( 0 ).onselectstart = function ( e ) { return false; }; this.tableWrapper = this.obj.parents( '.' + this.get( 'classTableWrapper' ) ).get( 0 ); jQuery( this.cells ).each( function () { this.activate(); } ); // after the cells where replaced with contentEditables ... add selection cells // first add the additional columns on the left side this.attachSelectionColumn(); // then add the additional row at the top this.attachSelectionRow(); this.makeCaptionEditable(); this.checkWai(); this.isActive = true; Aloha.trigger( 'aloha-table-activated' ); }; /** * Make the table caption editable (if present) */ Table.prototype.makeCaptionEditable = function() { var caption = this.obj.find('caption').eq(0); if (caption) { this.tablePlugin.makeCaptionEditable(caption); } }; /** * check the WAI conformity of the table and sets the attribute. */ Table.prototype.checkWai = function () { var w = this.wai; if (!w) { return; } w.removeClass(this.get('waiGreen')); w.removeClass(this.get('waiRed')); // Y U NO explain why we must check that summary is longer than 5 characters? // http://cdn3.knowyourmeme.com/i/000/089/665/original/tumblr_l96b01l36p1qdhmifo1_500.jpg if (jQuery.trim(this.obj[0].summary) != '') { w.addClass(this.get('waiGreen')); } else { w.addClass(this.get('waiRed')); } }; /** * Add the selection-column to the left side of the table and attach the events * for selection rows * * @return void */ Table.prototype.attachSelectionColumn = function() { // create an empty cell var emptyCell = jQuery(''), rowIndex, columnToInsert, rowObj, that = this, rows, i; // set the unicode ' ' code emptyCell.html('\u00a0'); that = this; rows = this.obj.context.rows; // add a column before each first cell of each row for ( i = 0; i < rows.length; i++) { rowObj = jQuery(rows[i]); columnToInsert = emptyCell.clone(); columnToInsert.addClass(this.get('classSelectionColumn')); columnToInsert.css('width', this.get('selectionArea') + 'px'); //rowObj.find('td:first').before(columnToInsert); rowObj.prepend(columnToInsert); // rowIndex + 1 because an addtional row is still added rowIndex = i + 1; // this method sets the selection-events to the cell this.attachRowSelectionEventsToCell(columnToInsert); } }; /** * Binds the needed selection-mouse events to the given cell * * @param cell * The jquery object of the table-data field * @return void */ Table.prototype.attachRowSelectionEventsToCell = function(cell){ var that = this; // unbind eventually existing events of this cell cell.unbind('mousedown'); cell.unbind('mouseover'); // prevent ie from selecting the contents of the table cell.get(0).onselectstart = function() { return false; }; cell.bind('mousedown', function(e) { // set flag that the mouse is pressed //TODO to implement the mousedown-select effect not only must the //mousedown be set here but also be unset when the mouse button is //released. // that.mousedown = true; return that.rowSelectionMouseDown(e); }); cell.bind('mouseover', function(e){ // only select more crows if the mouse is pressed if ( that.mousedown ) { return that.rowSelectionMouseOver(e); } }); }; /** * Mouse-Down event for the selection-cells on the left side of the table * * @param jqEvent * the jquery-event object * @return void */ Table.prototype.rowSelectionMouseDown = function ( jqEvent ) { // focus the table (if not already done) this.focus(); // if no cells are selected, reset the selection-array if ( this.selection.selectedCells.length == 0 ) { this.rowsToSelect = []; } // set the origin-rowId of the mouse-click this.clickedRowId = jqEvent.currentTarget.parentNode.rowIndex; // set single column selection if ( jqEvent.metaKey ) { var arrayIndex = jQuery.inArray( this.clickedRowId, this.rowsToSelect ); if ( arrayIndex >= 0 ) { this.rowsToSelect.splice( arrayIndex, 1 ); } else { this.rowsToSelect.push( this.clickedRowId ); } // block of columns selection } else if ( jqEvent.shiftKey ) { this.rowsToSelect.sort( function( a, b ) { return a - b; } ); var start = this.rowsToSelect[ 0 ]; var end = this.clickedRowId; if ( start > end ) { start = end; end = this.rowsToSelect[ 0 ]; } this.rowsToSelect = []; for ( var i = start; i <= end; i++ ) { this.rowsToSelect.push( i ); } // single column } else { this.rowsToSelect = [ this.clickedRowId ]; } // mark the selection visual this.selectRows(); // prevent browser from selecting the table jqEvent.preventDefault(); // stop bubble, otherwise the mousedown of the table is called ... jqEvent.stopPropagation(); this.tablePlugin.summary.focus(); // prevent ff/chrome/safare from selecting the contents of the table return false; }; /** * The mouse-over event for the selection-cells on the left side of the table. * On mouse-over check which column was clicked, calculate the span between * clicked and mouse-overed cell and mark them as selected * * @param jqEvent * the jquery-event object * @return void */ Table.prototype.rowSelectionMouseOver = function (jqEvent) { var rowIndex = jqEvent.currentTarget.parentNode.rowIndex, indexInArray, start, end, i; // only select the row if the mouse was clicked and the clickedRowId isn't // from the selection-row (row-id = 0) if (this.mousedown && this.clickedRowId >= 0) { // select first cell //var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0); //jQuery(firstCell).get(0).focus(); indexInArray = jQuery.inArray(rowIndex, this.rowsToSelect); start = (rowIndex < this.clickedRowId) ? rowIndex : this.clickedRowId; end = (rowIndex < this.clickedRowId) ? this.clickedRowId : rowIndex; this.rowsToSelect = new Array(); for ( i = start; i <= end; i++) { this.rowsToSelect.push(i); } // this actually selects the rows this.selectRows(); // prevent browser from selecting the table jqEvent.preventDefault(); // stop bubble, otherwise the mousedown of the table is called ... jqEvent.stopPropagation(); // prevent ff/chrome/safare from selecting the contents of the table return false; } }; /** * Binds the needed selection-mouse events to the given cell * * @param cell * The jquery object of the table-data field * @return void */ Table.prototype.attachSelectionRow = function () { var that = this; // create an empty td var emptyCell = jQuery(''); emptyCell.html('\u00a0'); // get the number of columns in the table (first row) // iterate through all rows and find the maximum number of columns to add var numColumns = 0; for( var i = 0; i < this.obj.context.rows.length; i++ ){ var curNumColumns = 0; for( var j = 0; j < this.obj.context.rows[i].cells.length; j++ ){ var colspan = Utils.colspan( this.obj.context.rows[i].cells[j] ); curNumColumns += colspan; } if( numColumns < curNumColumns ) { numColumns = curNumColumns; } } var selectionRow = jQuery(''); selectionRow.addClass(this.get('classSelectionRow')); selectionRow.css('height', this.get('selectionArea') + 'px'); for (var i = 0; i < numColumns; i++) { var columnToInsert = emptyCell.clone(); // the first cell should have no function, so only attach the events for // the rest if (i > 0) { // bind all mouse-events to the cell this.attachColumnSelectEventsToCell(columnToInsert); //set the colspan of selection column to match the colspan of first row columns } else { var columnToInsert = jQuery('').clone(); columnToInsert.addClass(this.get('classLeftUpperCorner')); var clickHandler = function (e) { // select the Table that.focus(); that.selection.selectAll(); // set the selection type before updating the scope that.tablePlugin.activeTable.selection.selectionType = 'cell'; that.tablePlugin.updateFloatingMenuScope(); // As side-effect of the following call the focus // will be set on the first selected cell. // This will be overwritten with the summary // attribute-field, if the setting summaryinsidebar // is false. that._removeCursorSelection(); //If the summary should be modified in the sidebar //we activate the sidebar panel if (that.tablePlugin.settings.summaryinsidebar) { that.tablePlugin.sidebar.open(); that.tablePlugin.sidebarPanel.activate(that.obj); that.tablePlugin.sidebar.correctHeight(); } // jump in Summary field // attempting to focus on summary input field will occasionally result in the // following exception: //uncaught exception: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIDOMHTMLInputElement.setSelectionRange]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: src/dep/ext-3.2.1/ext-all.js :: :: line 11" data: no] // this occurs when the tab in which the summary field is contained is not visible // TODO: I'm adding a try catch clause here for the time being, but a proper solution, which addresses the problem of how to handle invisible fields ought to be persued. try { that.tablePlugin.summary.focus(); e.stopPropagation(); e.preventDefault(); } catch (e) { } return false; }; this.wai = jQuery('
').width(25).height(12).click(clickHandler); columnToInsert.append(this.wai); } // add the cell to the row selectionRow.append(columnToInsert); } // global mouseup event to reset the selection properties jQuery(document).bind('mouseup', function(e) { that.columnSelectionMouseUp(e) } ); this.obj.find('tr:first').before( selectionRow ); }; /** * Binds the events for the column selection to the given cell. * * @param cell * the jquery object of the td-field * @return void */ Table.prototype.attachColumnSelectEventsToCell = function ( cell ) { var that = this; // unbind eventually existing events of this cell cell.unbind( 'mousedown' ); cell.unbind( 'mouseover' ); // prevent ie from selecting the contents of the table cell.get( 0 ).onselectstart = function () { return false; }; cell.bind( 'mousedown', function ( e ) { that.columnSelectionMouseDown( e ) } ); cell.bind( 'mouseover', function ( e ) { that.columnSelectionMouseOver( e ) } ); }; /** * Handles the mouse-down event for the selection-cells on the top of the * menu * * @param {jQuery:Event} jqEvent - the jquery-event object * @return void */ Table.prototype.columnSelectionMouseDown = function ( jqEvent ) { // focus the table (if not already done) this.focus(); // if no cells are selected, reset the selection-array if ( this.selection.selectedCells.length == 0 ) { this.columnsToSelect = []; } // set the origin-columnId of the mouse-click this.clickedColumnId = jQuery( jqEvent.currentTarget.parentNode ) .children().index( jqEvent.currentTarget ); // set single column selection if ( jqEvent.metaKey ) { var arrayIndex = jQuery.inArray( this.clickedColumnId, this.columnsToSelect ); if ( arrayIndex >= 0 ) { this.columnsToSelect.splice( arrayIndex, 1 ); } else { this.columnsToSelect.push( this.clickedColumnId ); } // block of columns selection } else if ( jqEvent.shiftKey ) { this.columnsToSelect.sort( function( a, b ) { return a - b; } ); var start = this.columnsToSelect[ 0 ]; var end = this.clickedColumnId; if ( start > end ) { start = end; end = this.columnsToSelect[ 0 ]; } this.columnsToSelect = []; for ( var i = start; i <= end; i++ ) { this.columnsToSelect.push( i ); } // single column } else { this.columnsToSelect = [ this.clickedColumnId ]; } // mark the selection visual this.selectColumns(); // prevent browser from selecting the table jqEvent.preventDefault(); // stop bubble, otherwise the mousedown of the table is called ... jqEvent.stopPropagation(); // prevent ff/chrome/safare from selecting the contents of the table return false; }; /** * Mouseover-event for the column-selection cell. This method calcluates the * span between the clicked column and the mouse-overed cell and selects the * columns inbetween. and mark them as selected * * @param jqEvent * the jquery-event object * @return void */ Table.prototype.columnSelectionMouseOver = function (jqEvent) { var colIdx = jqEvent.currentTarget.cellIndex, columnsToSelect = [], start, end; // select all columns from the last clicked to the hoverd if ( this.mouseDownColIdx ) { start = (colIdx < this.mouseDownColIdx) ? colIdx : this.mouseDownColIdx; end = (colIdx < this.mouseDownColIdx) ? this.mouseDownColIdx : colIdx; for (var i = start; i <= end; i++) { columnsToSelect.push(i); } this.selectColumns( columnsToSelect ); } }; /** * MouseUp-event for the column-selection. This method resets the * selection mode * * @param jqEvent * the jquery-event object * @return void */ Table.prototype.columnSelectionMouseUp = function (jqEvent) { this.mouseDownColIdx = false; }; /** * Deletes the selected rows. If no row are selected, delete the row, where the * cursor is positioned. If all rows of the table should be deleted, the whole * table is deletet and removed from the tableRegistry. * * @return void */ Table.prototype.deleteRows = function() { var rowIDs = [], rowsToDelete = {}, table = this; // if a selection was made, delete the selected cells if (0 === this.selection.selectedCells.length) { return; } for (var i = 0; i < this.selection.selectedCells.length; i++) { rowsToDelete[this.selection.selectedCells[i].parentNode.rowIndex] = true; } for (rowId in rowsToDelete) { rowIDs.push(rowId); } // if all rows should be deleted, set a flag to remove the WHOLE table var deleteTable = false; if (rowIDs.length == this.numRows) { deleteTable = true; } // delete the whole table if (deleteTable) { var that = this; Dialog.confirm({ title : i18n.t('Table'), text : i18n.t('deletetable.confirm'), yes : function () { that.deleteTable(); } }); } else { rowIDs.sort(function(a,b){return a - b;}); // check which cell should be focused after the deletion var focusRowId = rowIDs[0]; if (focusRowId > (this.numRows - rowIDs.length)) { focusRowId --; } // get all rows var rows = this.getRows(); //splits all cells on the rows to be deleted jQuery.each( rowIDs, function ( unused, rowId ) { var row = rows[ rowId ]; for (var i = 0; i < row.cells.length; i++) { Utils.splitCell( row.cells[i], function () { return table.newActiveCell().obj; }); } }); //decreases rowspans of cells that span the row to be deleted //and removes the row var grid = Utils.makeGrid( rows ); jQuery.each( rowIDs, function ( unused, rowId ) { var row = grid[ rowId ]; for ( var j = 0; j < row.length; ) { var cellInfo = row[ j ]; var rowspan = Utils.rowspan( cellInfo.cell ); if ( 1 < rowspan ) { jQuery( cellInfo.cell ).attr( 'rowspan', rowspan - 1); } j += cellInfo.colspan; } jQuery( rows[ rowId ] ).remove(); }); // reduce the attribute storing the number of rows in the table this.numRows -= rowIDs.length; // IE needs a timeout to work properly window.setTimeout( function() { var lastCell = jQuery( rows[1].cells[ focusRowId +1 ] ); lastCell.focus(); }, 5); // finally unselect the marked cells this.selection.unselectCells(); } }; /** * Deletes the selected columns. If no columns are selected, delete the column, where the * cursor is positioned. If all columns of the table should be deleted, the whole * table is deleted from the dom and removed from the tableRegistry. * * @return void */ Table.prototype.deleteColumns = function() { var colIDs = [], cellToDelete = [], // get all rows to iterate rows = this.getRows(), that = this, changeColspan = [], cells, cellInfo; var grid = Utils.makeGrid(rows); var selectColWidth = 1; //width of the select-row column // if all columns should be deleted, remove the WHOLE table // delete the whole table if ( this.selection.selectedColumnIdxs.length == grid[0].length - selectColWidth ) { Dialog.confirm({ title : i18n.t('Table'), text : i18n.t('deletetable.confirm'), yes : function () { that.deleteTable(); } }); } else { colIDs.sort(function(a,b) {return a - b;} ); //TODO there is a bug that that occurs if a column is //selected and deleted, and then a column with a greater //x-index is selected and deleted. //sorted so we delete from right to left to minimize interfernce of deleted rows var gridColumns = this.selection.selectedColumnIdxs.sort(function(a,b){ return b - a; }); for (var i = 0; i < gridColumns.length; i++) { var gridColumn = gridColumns[i]; for (var j = 0; j < rows.length; j++) { var cellInfo = grid[j][gridColumn]; if ( ! cellInfo ) { //TODO this case occurred because of a bug somewhere which should be fixed continue; } if ( 0 === cellInfo.spannedX ) { if (1 < cellInfo.colspan) { var nCell = this.newActiveCell().obj; jQuery( cellInfo.cell ).after(nCell); nCell.attr('rowspan', cellInfo.rowspan); nCell.attr('colspan', cellInfo.colspan - 1); } jQuery( cellInfo.cell ).remove(); } else { jQuery( cellInfo.cell ).attr('colspan', cellInfo.colspan - 1); } //ensures that always 0 === cellInfo.spannedY j += cellInfo.rowspan - 1; } //rebuild the grid to reflect the table structure change grid = Utils.makeGrid(rows); } // reduce the attribute storing the number of rows in the table this.numCols -= colIDs.length; // IE needs a timeout to work properly window.setTimeout( function() { var lastCell = jQuery( rows[1].cells[1] ); lastCell.focus(); }, 5); this.selection.unselectCells(); } }; /** * Deletes the table from the dom and remove it from the tableRegistry. * * @return void */ Table.prototype.deleteTable = function() { var deleteIndex = -1; for (var i = 0; i < this.tablePlugin.TableRegistry.length; i++){ if (this.tablePlugin.TableRegistry[i].obj.attr('id') == this.obj.attr('id')) { deleteIndex = i; break; } } if (deleteIndex >= 0) { // before deleting the table, deactivate it this.deactivate(); this.selection.selectionType = undefined; this.tablePlugin.TableRegistry.splice(i, 1); // we will set the cursor right before the removed table var newRange = Aloha.Selection.rangeObject; // TODO set the correct range here (cursor shall be right before the removed table) newRange.endContainer = this.obj.get(0).parentNode; newRange.startContainer = newRange.endContainer; newRange.endOffset = GENTICS.Utils.Dom.getIndexInParent(this.obj.get(0)); newRange.startOffset = newRange.endOffset; newRange.clearCaches(); this.obj.remove(); this.parentEditable.obj.focus(); // select the new range newRange.correctRange(); newRange.select(); } }; /** * @param {string} position * could be 'after' or 'before'. defines the position where the new * rows should be inserted */ function rowIndexFromSelection( position, selection ) { var newRowIndex = -1; // get the index where the new rows should be inserted var cellOfInterest = null; if ( 'before' === position ) { cellOfInterest = selection.selectedCells[ 0 ]; } else if ( 'after' === position ) { var offset = selection.selectedCells.length - 1; cellOfInterest = selection.selectedCells[ offset ]; } if (cellOfInterest && cellOfInterest.nodeType == 1) { newRowIndex = cellOfInterest.parentNode.rowIndex; } return newRowIndex; } /** * Wrapper function for this.addRow to add a row before the active row * * @see Table.prototype.addRow */ Table.prototype.addRowBeforeSelection = function(highlightNewRows) { var newRowIndex = rowIndexFromSelection( 'before', this.selection ); if ( -1 !== newRowIndex ) { this.addRow( newRowIndex ); } }; /** * Wrapper function for this.addRow to add a row after the active row * * @see Table.prototype.addRow */ Table.prototype.addRowAfterSelection = function() { var newRowIndex = rowIndexFromSelection( 'after', this.selection ); if ( -1 !== newRowIndex ) { this.addRow( newRowIndex + 1 ); } }; /** * Adds a new row to the table. * * @param {int} rowIndex * the index at which the new row shall be inserted */ Table.prototype.addRow = function(newRowIndex) { var that = this; var rowsToInsert = 1; var numCols = this.countVirtualCols(); var $rows = this.obj.children().children('tr'); for (var j = 0; j < rowsToInsert; j++) { var insertionRow = jQuery(''); // create the first column, the "select row" column var selectionColumn = jQuery(''); selectionColumn.addClass(this.get('classSelectionColumn')); this.attachRowSelectionEventsToCell(selectionColumn); insertionRow.append(selectionColumn); var grid = Utils.makeGrid($rows); var selectColOffset = 1; if ( newRowIndex >= grid.length ) { for (var i = selectColOffset; i < grid[0].length; i++) { insertionRow.append(this.newActiveCell().obj); } } else { for (var i = selectColOffset; i < grid[newRowIndex].length; ) { var cellInfo = grid[newRowIndex][i]; if (Utils.containsDomCell(cellInfo)) { var colspan = cellInfo.colspan; while (colspan--) { insertionRow.append(this.newActiveCell().obj); } } else { jQuery( cellInfo.cell ).attr('rowspan', cellInfo.rowspan + 1); } i += cellInfo.colspan; } } if ( newRowIndex >= $rows.length ) { $rows.eq( $rows.length - 1 ).after( insertionRow ); } else { $rows.eq( newRowIndex ).before( insertionRow ); } } this.numRows += rowsToInsert; }; /** * Wrapper method to add columns on the right side * * @see Table.addColumns * @return void */ Table.prototype.addColumnsRight = function () { this.addColumns('right'); }; /** * Wrapper method to add columns on the left side * * @see Table.addColumns * @return void */ Table.prototype.addColumnsLeft = function() { this.addColumns('left'); }; /** * Inserts new columns into the table. Either on the right or left side. If * columns are selected, the amount of selected columns will be inserted on the * 'right' or 'left' side. If no cells are selected, 1 new column will be * inserted before/after the column of the last active cell. * As well all column-selection events must be bound to the firsts row-cell. * * @param position * could be 'left' or 'right'. defines the position where the new * columns should be inserted * @return void */ Table.prototype.addColumns = function( position ) { var that = this, emptyCell = jQuery( '' ), rows = this.getRows(), cell, currentColIdx, columnsToSelect = [], selectedColumnIdxs = this.selection.selectedColumnIdxs; if ( 0 === selectedColumnIdxs.length ) { return; } selectedColumnIdxs.sort( function ( a, b ) { return a - b; } ); // refuse to insert a column unless a consecutive range has been selected if ( ! Utils.isConsecutive( selectedColumnIdxs ) ) { Dialog.alert( { title : i18n.t( 'Table' ), text : i18n.t( 'table.addColumns.nonConsecutive' ) }); return; } if ( 'left' === position ) { currentColIdx = selectedColumnIdxs[ 0 ]; // inserting a row before the selected column indicies moves // all selected columns one to the right for ( var i = 0; i < this.selection.selectedColumnIdxs.length; i++ ) { this.selection.selectedColumnIdxs[ i ] += 1; } } else {//"right" == position currentColIdx = selectedColumnIdxs[ selectedColumnIdxs.length - 1 ]; } var grid = Utils.makeGrid( rows ); for ( var i = 0; i < rows.length; i++ ) { // prepare the cell to be inserted cell = emptyCell.clone(); cell.html( '\u00a0' ); // on first row correct the position of the selected columns if ( i == 0 ) { // this is the first row, so make a column-selection cell this.attachColumnSelectEventsToCell( cell ); } else { // activate the cell for this table cellObj = this.newActiveCell( cell.get(0) ); cell = cellObj.obj; } var leftCell = Utils.leftDomCell( grid, i, currentColIdx ); if ( null == leftCell ) { jQuery( rows[i] ).prepend( cell ); } else { if ( 'left' === position && Utils.containsDomCell( grid[ i ][ currentColIdx ] ) ) { jQuery( leftCell ).before( cell ); } else {//right jQuery( leftCell ).after( cell ); } } this.numCols++; } }; /** * Helper method to set the focus-attribute of the table to true * * @return void */ Table.prototype.focus = function() { if (!this.hasFocus) { if (!this.parentEditable.isActive) { this.parentEditable.obj.focus(); } // @iefix this.tablePlugin.setFocusedTable(this); // select first cell // TODO put cursor in first cell without selecting //var firstCell = this.obj.find('tr:nth-child(2) td:nth-child(2)').children('div[contenteditable=true]').get(0); //jQuery(firstCell).get(0).focus(); } // TODO workaround - fix this. the selection is updated later on by the browser // using setTimeout here is hideous, but a simple execution-time call will fail // DEACTIVATED by Haymo prevents selecting rows // setTimeout('Aloha.Selection.updateSelection(false, true)', 50); }; /** * Helper method to set the focus-attribute of the table to false * * @return void */ Table.prototype.focusOut = function() { if (this.hasFocus) { this.tablePlugin.setFocusedTable(undefined); this.selection.selectionType = undefined; } }; /** * Undoes the cursor-selection after cells have been selected. This * is done to be more consistent in the UI - there should either be * a cursor-selection or a cell-selection, but not both. */ Table.prototype._removeCursorSelection = function() { // We can't remove the selection on IE because whenever a // row/column is selected, and then another row/column is // selected, the browser windows scrolls to the top of the page // (som kind of browser bug). // This is no problem for IE because IE removes the // cursor-selection by itself and shows a frame around the // table, with resize handles (the frame seems useless). // On other browsers, we can't remove the selection because the // floating menu will disappear when one selects a rows/column // and types a key (that's the same effect as when one clicks // outside the editable). //TODO: currently, removing the cursor selection can't be // reliably implemented. //if ( ! jQuery.browser.msie ) { // Aloha.getSelection().removeAllRanges(); //} // The following is a workaround for the above because we can't // leave the cursor-selection outside of the table, since // otherwise the floating menu scope will be incorrect when one // CTRL-clicks on the rows or columns. var selection = Aloha.getSelection(); if ( !selection || !selection._nativeSelection || selection._nativeSelection._ranges.length == 0 ) { return; } var range = selection.getRangeAt( 0 ); if ( null == range.startContainer ) { return; } // if the selection is already in the table, do nothing if ( 0 !== jQuery( range.startContainer ).closest('table').length ) { return; } // if no cells are selected, do nothing if ( 0 === this.selection.selectedCells.length ) { return; } // set the foces to the first selected cell var container = TableCell.getContainer( this.selection.selectedCells[ 0 ] ); jQuery( container ).focus(); } /** * Marks all cells of the specified column as marked (adds a special class) * * @return void */ Table.prototype.selectColumns = function ( columns ) { var columnsToSelect; if ( columns ) { columnsToSelect = columns; } else { columnsToSelect = this.columnsToSelect; } // ====== BEGIN UI specific code - should be handled on event aloha-table-selection-changed by UI ======= // activate all column formatting button for ( var i = 0; i < this.tablePlugin.columnMSItems.length; i++ ) { this.tablePlugin.columnMSButton.showItem(this.tablePlugin.columnMSItems[i].name); } Scopes.setScope(this.tablePlugin.name + '.column'); this.tablePlugin._columnheaderButton.setState(this.selection.isHeader()); var rows = this.getRows(); // set the first class found as active item in the multisplit button this.tablePlugin.columnMSButton.setActiveItem(); for (var k = 0; k < this.tablePlugin.columnConfig.length; k++) { if ( jQuery(rows[0].cells[0]).hasClass(this.tablePlugin.columnConfig[k].cssClass) ) { this.tablePlugin.columnMSButton.setActiveItem(this.tablePlugin.columnConfig[k].name); k = this.tablePlugin.columnConfig.length; } } // ====== END UI specific code - should be handled by UI ======= // blur all editables within the table this.obj.find('div.aloha-ui-table-cell-editable').blur(); this.selection.selectColumns( columnsToSelect ); this.selection.notifyCellsSelected(); this._removeCursorSelection(); }; /** * Marks all cells of the specified row as marked (adds a special class) * * @return void */ Table.prototype.selectRows = function () { // activate all row formatting button for (var i = 0; i < this.tablePlugin.rowMSItems.length; i++ ) { this.tablePlugin.rowMSButton.showItem(this.tablePlugin.rowMSItems[i].name); } for (var i = 0; i < this.rowsToSelect.length; i++) { var rowId = this.rowsToSelect[i]; var rowCells = jQuery(this.getRows()[rowId].cells).toArray(); if (i == 0) { // set the first class found as active item in the multisplit button for (var j = 0; j < rowCells.length; j++) { this.tablePlugin.rowMSButton.setActiveItem(); for ( var k = 0; k < this.tablePlugin.rowConfig.length; k++) { if (jQuery(rowCells[j]).hasClass(this.tablePlugin.rowConfig[k].cssClass) ) { this.tablePlugin.rowMSButton.setActiveItem(this.tablePlugin.rowConfig[k].name); k = this.tablePlugin.rowConfig.length; } } } } } // TableSelection.selectionType = 'row'; Scopes.setScope(this.tablePlugin.name + '.row'); this.selection.selectRows( this.rowsToSelect ); this.tablePlugin._rowheaderButton.setState(this.selection.isHeader()); // blur all editables within the table this.obj.find('div.aloha-ui-table-cell-editable').blur(); this.selection.notifyCellsSelected(); this._removeCursorSelection(); }; /** * Deactivation of a Aloha-table. Clean up ... remove the wrapping div and the * selection-helper divs * * @return void */ Table.prototype.deactivate = function() { // unblockify the table wrapper var parent = this.obj.parent(); if (parent.mahaloBlock) { parent.mahaloBlock(); } this.obj.removeClass(this.get('className')); if (jQuery.trim(this.obj.attr('class')) == '') { this.obj.removeAttr('class'); } this.obj.removeAttr('contenteditable'); // this.obj.removeAttr('id'); // unwrap the selectionLeft-div if available if (this.obj.parents('.' + this.get('classTableWrapper')).length){ this.obj.unwrap(); } // remove the selection row this.obj.find('tr.' + this.get('classSelectionRow') + ':first').remove(); // remove the selection column (first column left) var that = this; jQuery.each(this.obj.context.rows, function(){ jQuery(this).children('td.' + that.get('classSelectionColumn')).remove(); }); // remove the "selection class" from all td and th in the table this.obj.find('td, th').removeClass(this.get('classCellSelected')); this.obj.find('td, th').removeClass('aloha-table-cell_active'); this.obj.unbind(); this.obj.children('tbody').unbind(); // wrap the inner html of the contentEditable div to its outer html for (var i = 0; i < this.cells.length; i++) { var Cell = this.cells[i]; Cell.deactivate(); } // remove editable span in caption (if any) this.obj.find('caption div').each(function() { jQuery(this).contents().unwrap(); }); // better unset ;-) otherwise activate() may think you're activated. this.isActive = false; }; /** * Attach the event for column resize for the given cell. * @param {DOMElement} tableCell * * @return void */ Table.prototype.attachColumnResize = function(cell) { var that = this; //unbind any exisiting resize event handlers that.detachRowColResize( cell ); var rows = cell.closest( 'tbody' ).children( 'tr' ); var cellRow = cell.closest( 'tr' ); var gridId = Utils.cellIndexToGridColumn( rows, rows.index( cellRow ), cellRow.children().index( cell ) ); var resizeColumns = function(pixelsMoved) { var expandToWidth, reduceToWidth; Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) { var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] ); // skip the select & cells with colspans if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) { return true; } if (gridCi === gridId ) { if (!reduceToWidth) { reduceToWidth = currentCell.width() - pixelsMoved; } Utils.resizeCellWidth( currentCell, reduceToWidth ); } else if (gridCi === gridId - 1) { if (!expandToWidth) { expandToWidth = currentCell.width() + pixelsMoved; } Utils.resizeCellWidth( currentCell, expandToWidth ); } return true; }); }; cell.bind( 'mousedown.resize', function() { // create a guide var guide = jQuery( '
' ); guide.css({ 'height': jQuery( cell ).closest( 'tbody' ).innerHeight(), 'width': jQuery( cell ).outerWidth() - jQuery( cell ).innerWidth(), 'top': jQuery( cell ).closest( 'tbody' ).offset().top, 'left': jQuery( cell ).offset().left, 'position': 'absolute', 'background-color': '#80B5F2' }); jQuery( 'body' ).append( guide ); Utils.getCellResizeBoundaries(gridId, rows, function(maxPageX, minPageX) { // unset the selection type that.selection.resizeMode = true; // move the guide while dragging jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) { // limit the maximum resize if ( e.pageX > minPageX && e.pageX < maxPageX ) { guide.css( 'left', e.pageX ); } }); // do the actual resizing after drag stops jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) { var pixelsMoved = 0; if ( e.pageX < minPageX ) { pixelsMoved = minPageX - cell.offset().left; } else if ( e.pageX > minPageX && e.pageX < maxPageX ) { pixelsMoved = e.pageX - cell.offset().left; } else if ( e.pageX > maxPageX ) { pixelsMoved = maxPageX - cell.offset().left; } if ( pixelsMoved !== 0 ) { resizeColumns( pixelsMoved ); } jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' ); jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' ); // unset the selection resize mode that.selection.resizeMode = false; guide.remove(); }); }); }); }; /** * Attach the event handler for row resize for the given cell. * @param {DOMElement} tableCell * * @return void */ Table.prototype.attachRowResize = function(cell, lastRow) { var that = this; //unbind any exisiting resize event handlers that.detachRowColResize( cell ); var resizeRows = function(pixelsMoved) { var expandingRow; if (lastRow) { expandingRow = cell.closest( 'tr' ); } else { expandingRow = cell.closest( 'tr' ).prev( 'tr' ); } var currentRowHeight = expandingRow.height(); var expandToHeight = currentRowHeight + pixelsMoved; // correct if the height is a minus value if ( expandToHeight < 0 ) { expandToHeight = 1; } expandingRow.css( 'height', expandToHeight ); }; cell.bind( 'mousedown.resize', function(){ // create a guide var guide = jQuery( '
' ); var guideTop = function() { if (lastRow) { return cell.offset().top + cell.outerHeight(); } else { return cell.offset().top; } }; guide.css({ 'width': cell.closest( 'tbody' ).innerWidth(), 'height': cell.outerHeight() - cell.innerHeight(), 'top': guideTop(), 'left': cell.closest( 'tbody' ).offset().left, 'position': 'absolute', 'background-color': '#80B5F2' }); jQuery( 'body' ).append( guide ); // set the minimum resize var minHeight = function() { if (lastRow) { return cell.closest('tr').offset().top; } else { return cell.closest('tr').prev( 'tr' ).offset().top; } }; // set the selection resize mode that.selection.resizeMode = true; // move the guide while dragging jQuery( 'body' ).bind( 'mousemove.dnd_row_resize', function(e) { if ( e.pageY > minHeight() ) { guide.css( 'top', e.pageY ); } }); // do the actual resizing after drag stops jQuery( 'body' ).bind( 'mouseup.dnd_row_resize', function(e) { var pixelsMoved = 0; if (lastRow) { pixelsMoved = e.pageY - ( cell.offset().top + cell.outerHeight() ); } else { pixelsMoved = e.pageY - cell.offset().top; } resizeRows( pixelsMoved ); jQuery( 'body' ).unbind( 'mousemove.dnd_row_resize' ); jQuery( 'body' ).unbind( 'mouseup.dnd_row_resize' ); // unset the selection resize mode that.selection.resizeMode = false; guide.remove(); }); }); }; /** * Attach the table width resize event. * @param {DOMElement} table * * @return void */ Table.prototype.attachTableResizeWidth = function(table) { var that = this; var tableContainer = table.closest('.aloha-table-wrapper') var lastColumn = table.find("tr:not(.aloha-table-selectcolumn) td:last-child") var lastCell; jQuery.each( lastColumn, function() { // don't use colspanned cell as the base cell if ( !jQuery( this ).attr('colspan') || jQuery( this ).attr('colspan') < 2 ) { lastCell = jQuery( this ); return false; } }); // change the cursor lastColumn.css( 'cursor', 'col-resize' ); var resizeColumns = function(pixelsMoved) { var rows = table.find( 'tr' ); var lastCellRow = lastCell.closest( 'tr' ); var gridId = Utils.cellIndexToGridColumn( rows, rows.index( lastCellRow ), lastCellRow.children().index( lastCell ) ); var expandToWidth = pixelsMoved - Utils.getCellBorder(lastCell) - Utils.getCellPadding(lastCell); Utils.walkCells(rows, function(ri, ci, gridCi, colspan, rowspan) { var currentCell = jQuery( jQuery( rows[ri] ).children()[ ci ] ) // skip the select cells and cells with colspans if ( currentCell.hasClass( 'aloha-table-selectrow' ) || currentCell.closest( 'tr' ).hasClass( 'aloha-table-selectcolumn' ) || colspan > 1 ) { return true; } if (gridCi === gridId ) { Utils.resizeCellWidth( currentCell, expandToWidth ); } else { Utils.resizeCellWidth( currentCell, currentCell.width() ); } return true; }); }; lastColumn.bind( 'mousedown.resize', function() { // create a guide var guide = jQuery( '
' ); guide.css({ 'height': table.children( 'tbody' ).innerHeight(), 'width': lastCell.outerWidth() - lastCell.innerWidth(), 'top': table.find('tbody').offset().top, 'left': table.offset().left + table.outerWidth(), 'position': 'absolute', 'background-color': '#80B5F2' }); jQuery( 'body' ).append( guide ); // set the maximum and minimum resize var maxPageX = tableContainer.offset().left + tableContainer.width(); var minPageX = lastCell.offset().left + ( lastCell.innerWidth() - lastCell.width() ) + Utils.getMinColWidth( lastCell ); // unset the selection type that.selection.resizeMode = true; // move the guide while dragging jQuery( 'body' ).bind( 'mousemove.dnd_col_resize', function(e) { // limit the maximum resize if ( e.pageX > minPageX && e.pageX < maxPageX ) { guide.css( 'left', e.pageX ); } }); // do the actual resizing after drag stops jQuery( 'body' ).bind( 'mouseup.dnd_col_resize', function(e) { var pixelsMoved = 0; if ( e.pageX <= minPageX ) { pixelsMoved = minPageX - lastCell.offset().left; } else if ( e.pageX > minPageX && e.pageX < maxPageX ) { pixelsMoved = e.pageX - lastCell.offset().left; } else if ( e.pageX > maxPageX ) { pixelsMoved = maxPageX - lastCell.offset().left; } // set the table width resizeColumns( pixelsMoved ); // unbind the events and reset the cursor jQuery( 'body' ).unbind( 'mousemove.dnd_col_resize' ); jQuery( 'body' ).unbind( 'mouseup.dnd_col_resize' ); lastColumn.unbind( 'mousedown.resize' ); lastColumn.css( 'cursor', 'default' ); // unset the selection resize mode that.selection.resizeMode = false; guide.remove(); }); }); }; /** * Detach any column/row resize event handlers attached to the cell. * @param {DOMElement} tableCell * * @return void */ Table.prototype.detachRowColResize = function(cell) { return cell.unbind('mousedown.resize'); }; /** * toString-method for Table object * * @return void */ Table.prototype.toString = function() { return 'Table'; }; Table.prototype.newCell = function(domElement) { return new TableCell(domElement, this); }; Table.prototype.newActiveCell = function(domElement) { var cell = new TableCell(domElement, this); cell.activate(); return cell; }; /** * @return the rows of the table as an array of DOM nodes */ Table.prototype.getRows = function () { //W3C DOM property .rows supported by all modern browsers var rows = this.obj.get( 0 ).rows; //converts the HTMLCollection to a real array return jQuery.makeArray( rows ); }; return Table; });