2014-10-27 14:26:47 +00:00
|
|
|
/*!
|
|
|
|
* VisualEditor ContentEditable MWTableNode class.
|
|
|
|
*
|
2018-01-03 00:54:47 +00:00
|
|
|
* @copyright 2011-2018 VisualEditor Team and others; see AUTHORS.txt
|
2014-10-27 14:26:47 +00:00
|
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ContentEditable MW table node.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends ve.ce.TableNode
|
2016-01-25 16:35:49 +00:00
|
|
|
* @mixins ve.ce.ClassAttributeNode
|
2014-10-27 14:26:47 +00:00
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @param {ve.dm.MWTableNode} model Model to observe
|
|
|
|
* @param {Object} [config] Configuration options
|
|
|
|
*/
|
|
|
|
ve.ce.MWTableNode = function VeCeMWTableNode() {
|
|
|
|
// Parent constructor
|
|
|
|
ve.ce.MWTableNode.super.apply( this, arguments );
|
|
|
|
|
2016-01-25 16:35:49 +00:00
|
|
|
// Mixin constructors
|
|
|
|
ve.ce.ClassAttributeNode.call( this );
|
2017-01-18 06:52:57 +00:00
|
|
|
|
|
|
|
// Properties
|
|
|
|
this.updateSortableHeadersHandler = ve.debounce( this.updateSortableHeaders );
|
|
|
|
this.$sortableHeaders = $( [] );
|
|
|
|
|
|
|
|
// Events
|
|
|
|
this.connect( this, { setup: 'updateSortableHeadersHandler' } );
|
|
|
|
this.model.connect( this, {
|
|
|
|
// Update when the table is made sortable or not sortable
|
|
|
|
attributeChange: 'updateSortableHeadersHandler',
|
|
|
|
// Update when a cell style changes between content cell and header cell
|
|
|
|
cellAttributeChange: 'updateSortableHeadersHandler'
|
|
|
|
} );
|
|
|
|
this.model.getMatrix().connect( this, {
|
|
|
|
// Update when cells are added, removed, merged, split
|
|
|
|
structureChange: 'updateSortableHeadersHandler'
|
|
|
|
} );
|
|
|
|
|
|
|
|
// DOM changes
|
|
|
|
this.$element.addClass( 've-ce-mwTableNode' );
|
2014-10-27 14:26:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* Inheritance */
|
|
|
|
|
|
|
|
OO.inheritClass( ve.ce.MWTableNode, ve.ce.TableNode );
|
|
|
|
|
2016-01-25 16:35:49 +00:00
|
|
|
OO.mixinClass( ve.ce.MWTableNode, ve.ce.ClassAttributeNode );
|
|
|
|
|
2014-10-27 14:26:47 +00:00
|
|
|
/* Static Properties */
|
|
|
|
|
|
|
|
ve.ce.MWTableNode.static.name = 'mwTable';
|
|
|
|
|
2017-12-02 09:53:28 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
ve.ce.MWTableNode.prototype.destroy = function () {
|
|
|
|
this.model.getMatrix().disconnect( this );
|
|
|
|
|
|
|
|
// Parent method
|
|
|
|
ve.ce.MWTableNode.super.prototype.destroy.apply( this, arguments );
|
|
|
|
};
|
|
|
|
|
2017-01-18 06:52:57 +00:00
|
|
|
/**
|
|
|
|
* Update sortable headers (if the table is sortable).
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
ve.ce.MWTableNode.prototype.updateSortableHeaders = function () {
|
|
|
|
var
|
|
|
|
view = this,
|
|
|
|
cellModels, cellViews;
|
|
|
|
|
2017-12-02 09:53:28 +00:00
|
|
|
if ( !this.model ) {
|
|
|
|
// Fired after teardown due to debounce
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-10 19:35:13 +00:00
|
|
|
if ( this.model.getAttribute( 'collapsible' ) ) {
|
|
|
|
mw.loader.load( 'jquery.makeCollapsible.styles' );
|
|
|
|
}
|
|
|
|
|
2017-01-18 06:52:57 +00:00
|
|
|
this.$element.toggleClass( 'jquery-tablesorter', this.model.getAttribute( 'sortable' ) );
|
|
|
|
|
|
|
|
this.$sortableHeaders.removeClass( 'headerSort' );
|
|
|
|
|
|
|
|
if ( this.model.getAttribute( 'sortable' ) ) {
|
|
|
|
// We only really want the styles. But it's harmless to load the entire module, and if the user
|
|
|
|
// ends up saving this change, it will be loaded anyway to render the real sortable table.
|
|
|
|
mw.loader.load( 'jquery.tablesorter' );
|
|
|
|
|
|
|
|
cellModels = this.getTablesorterHeaderCells();
|
|
|
|
cellViews = cellModels.map( function ( cellModel ) {
|
|
|
|
return view.getNodeFromOffset( cellModel.getOffset() - view.model.getOffset() );
|
|
|
|
} );
|
|
|
|
|
|
|
|
this.$sortableHeaders = $( cellViews.map( function ( cell ) {
|
|
|
|
return cell.$element[ 0 ];
|
|
|
|
} ) ).not( '.unsortable' );
|
|
|
|
} else {
|
|
|
|
this.$sortableHeaders = $( [] );
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$sortableHeaders.addClass( 'headerSort' );
|
|
|
|
|
|
|
|
// .headerSort class adds some padding, causing the overlays to become misaligned
|
|
|
|
this.updateOverlay();
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the last of header rows with maximum number of cells (minimum number of colspans) and return
|
|
|
|
* all of its cells. These are the cells that serve as sortable headers in jQuery Tablesorter.
|
|
|
|
* This algorithm is exactly the same, see the buildHeaders() function in jquery.tablesorter.js.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @return {ve.dm.TableCellNode[]}
|
|
|
|
*/
|
|
|
|
ve.ce.MWTableNode.prototype.getTablesorterHeaderCells = function () {
|
|
|
|
var
|
|
|
|
matrix = this.model.getMatrix(),
|
|
|
|
longestRow = [],
|
|
|
|
longestRowLength = 0,
|
|
|
|
i, l, matrixCells, isAllHeaders, rowLength, cellModels;
|
|
|
|
|
|
|
|
for ( i = 0, l = matrix.getRowCount(); i < l; i++ ) {
|
|
|
|
matrixCells = matrix.getRow( i );
|
|
|
|
cellModels = OO.unique( matrixCells.map( function ( matrixCell ) {
|
2018-04-19 13:27:22 +00:00
|
|
|
return matrixCell && matrixCell.getOwner().node;
|
2017-01-18 06:52:57 +00:00
|
|
|
} ) );
|
|
|
|
isAllHeaders = cellModels.every( function ( cellModel ) {
|
2018-04-19 13:27:22 +00:00
|
|
|
return cellModel && cellModel.getAttribute( 'style' ) === 'header';
|
2017-01-18 06:52:57 +00:00
|
|
|
} );
|
|
|
|
if ( !isAllHeaders ) {
|
|
|
|
// This is the end of table head (thead), stop looking further
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
rowLength = cellModels.length;
|
|
|
|
if ( rowLength >= longestRowLength ) {
|
|
|
|
longestRowLength = rowLength;
|
|
|
|
longestRow = cellModels;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return longestRow;
|
|
|
|
};
|
|
|
|
|
2014-10-27 14:26:47 +00:00
|
|
|
/* Registration */
|
|
|
|
|
|
|
|
ve.ce.nodeFactory.register( ve.ce.MWTableNode );
|