/**
* 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;
});
| |