File: ve.ce.MWTableNode.js

package info (click to toggle)
mediawiki 1%3A1.43.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 417,464 kB
  • sloc: php: 1,062,949; javascript: 664,290; sql: 9,714; python: 5,458; xml: 3,489; sh: 1,131; makefile: 64
file content (140 lines) | stat: -rw-r--r-- 4,155 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/*!
 * VisualEditor ContentEditable MWTableNode class.
 *
 * @copyright See AUTHORS.txt
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * ContentEditable MW table node.
 *
 * @class
 * @extends ve.ce.TableNode
 * @mixes ve.ce.ClassAttributeNode
 *
 * @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 );

	// Mixin constructors
	ve.ce.ClassAttributeNode.call( this );

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

/* Inheritance */

OO.inheritClass( ve.ce.MWTableNode, ve.ce.TableNode );

OO.mixinClass( ve.ce.MWTableNode, ve.ce.ClassAttributeNode );

/* Static Properties */

ve.ce.MWTableNode.static.name = 'mwTable';

/* Methods */

/**
 * @inheritdoc
 */
ve.ce.MWTableNode.prototype.destroy = function () {
	this.model.getMatrix().disconnect( this );

	// Parent method
	ve.ce.MWTableNode.super.prototype.destroy.apply( this, arguments );
};

/**
 * Update sortable headers (if the table is sortable).
 *
 * @private
 */
ve.ce.MWTableNode.prototype.updateSortableHeaders = function () {
	if ( !this.model ) {
		// Fired after teardown due to debounce
		return;
	}

	if ( this.model.getAttribute( 'collapsible' ) ) {
		mw.loader.load( 'jquery.makeCollapsible.styles' );
	}

	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' );

		const cellModels = this.getTablesorterHeaderCells();
		const cellViews = cellModels.map( ( cellModel ) => this.getNodeFromOffset( cellModel.getOffset() - this.model.getOffset() ) );

		this.$sortableHeaders = $( cellViews.map( ( cell ) => 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 () {
	const matrix = this.model.getMatrix();

	let longestRow = [];
	let longestRowLength = 0;
	for ( let i = 0, l = matrix.getRowCount(); i < l; i++ ) {
		const matrixCells = matrix.getRow( i );
		const cellModels = OO.unique( matrixCells.map( ( matrixCell ) => matrixCell && matrixCell.getOwner().node ) );
		const isAllHeaders = cellModels.every( ( cellModel ) => cellModel && cellModel.getAttribute( 'style' ) === 'header' );
		if ( !isAllHeaders ) {
			// This is the end of table head (thead), stop looking further
			break;
		}
		const rowLength = cellModels.length;
		if ( rowLength >= longestRowLength ) {
			longestRowLength = rowLength;
			longestRow = cellModels;
		}
	}

	return longestRow;
};

/* Registration */

ve.ce.nodeFactory.register( ve.ce.MWTableNode );