Merge "Introduce table widget, upstreamed from the Graph extension"
This commit is contained in:
commit
64f09f737a
11 changed files with 1964 additions and 0 deletions
|
|
@ -4105,6 +4105,7 @@
|
|||
"mw-widgets-dateinput-placeholder-month": "YYYY-MM",
|
||||
"mw-widgets-mediasearch-input-placeholder": "Search for media",
|
||||
"mw-widgets-mediasearch-noresults": "No results found.",
|
||||
"mw-widgets-table-row-delete": "Delete row",
|
||||
"mw-widgets-titleinput-description-new-page": "page does not exist yet",
|
||||
"mw-widgets-titleinput-description-redirect": "redirect to $1",
|
||||
"mw-widgets-categoryselector-add-category-placeholder": "Add a category...",
|
||||
|
|
|
|||
|
|
@ -4320,6 +4320,7 @@
|
|||
"mw-widgets-dateinput-placeholder-month": "Placeholder displayed in a date input field when it's empty, representing a date format with 4 digits for year and 2 digits for month, separated with hyphens (without a day). This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
|
||||
"mw-widgets-mediasearch-input-placeholder": "Place holder text for media search input",
|
||||
"mw-widgets-mediasearch-noresults": "Label notifying the user no results were found for the media search.",
|
||||
"mw-widgets-table-row-delete": "Tooltip for the delete row button in table widgets",
|
||||
"mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.",
|
||||
"mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget.",
|
||||
"mw-widgets-categoryselector-add-category-placeholder": "Placeholder displayed in the category selector widget after the capsules of already added categories.",
|
||||
|
|
|
|||
|
|
@ -2564,6 +2564,25 @@ return [
|
|||
],
|
||||
'targets' => [ 'desktop', 'mobile' ],
|
||||
],
|
||||
'mediawiki.widgets.Table' => [
|
||||
'scripts' => [
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.RowWidget.js',
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.RowWidgetModel.js',
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.TableWidget.js',
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.TableWidgetModel.js'
|
||||
],
|
||||
'styles' => [
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.RowWidget.css',
|
||||
'resources/src/mediawiki.widgets/Table/mw.widgets.TableWidget.css',
|
||||
],
|
||||
'dependencies' => [
|
||||
'oojs-ui-widgets'
|
||||
],
|
||||
'messages' => [
|
||||
'mw-widgets-table-row-delete',
|
||||
],
|
||||
'targets' => [ 'desktop', 'mobile' ],
|
||||
],
|
||||
'mediawiki.widgets.UserInputWidget' => [
|
||||
'scripts' => [
|
||||
'resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
.mw-widgets-rowWidget {
|
||||
clear: left;
|
||||
float: left;
|
||||
margin-bottom: -1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mw-widgets-rowWidget-label {
|
||||
display: block;
|
||||
margin-right: 5%;
|
||||
padding-top: 0.5em;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.mw-widgets-rowWidget > .mw-widgets-rowWidget-label {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mw-widgets-rowWidget > .mw-widgets-rowWidget-cells {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.mw-widgets-rowWidget > .mw-widgets-rowWidget-cells > .oo-ui-inputWidget {
|
||||
float: left;
|
||||
margin-right: -1px;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
.mw-widgets-rowWidget > .mw-widgets-rowWidget-cells > .oo-ui-inputWidget > input,
|
||||
.mw-widgets-rowWidget > .mw-widgets-rowWidget-delete-button > .oo-ui-buttonElement-button {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
336
resources/src/mediawiki.widgets/Table/mw.widgets.RowWidget.js
Normal file
336
resources/src/mediawiki.widgets/Table/mw.widgets.RowWidget.js
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
/**
|
||||
* A RowWidget is used in conjunction with {@link mw.widgets.TableWidget table widgets}
|
||||
* and should not be instantiated by themselves. They group together
|
||||
* {@link OO.ui.TextInputWidget text input widgets} to form a unified row of
|
||||
* editable data.
|
||||
*
|
||||
* @class
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.GroupElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} [config] Configuration options
|
||||
* @cfg {Array} [data] The data of the cells
|
||||
* @cfg {Array} [keys] An array of keys for easy cell selection
|
||||
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
|
||||
* @cfg {number} [index] The row index.
|
||||
* @cfg {string} [label] The row label to display. If not provided, the row index will
|
||||
* be used be default. If set to null, no label will be displayed.
|
||||
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
|
||||
* @cfg {boolean} [deletable=true] Whether the table should provide deletion UI tools
|
||||
* for this row or not. Defaults to true.
|
||||
*/
|
||||
mw.widgets.RowWidget = function MwWidgetsRowWidget( config ) {
|
||||
config = config || {};
|
||||
|
||||
// Parent constructor
|
||||
mw.widgets.RowWidget.super.call( this, config );
|
||||
|
||||
// Mixin constructor
|
||||
OO.ui.mixin.GroupElement.call( this, config );
|
||||
|
||||
// Set up model
|
||||
this.model = new mw.widgets.RowWidgetModel( config );
|
||||
|
||||
// Set up group element
|
||||
this.setGroupElement(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-widgets-rowWidget-cells' )
|
||||
);
|
||||
|
||||
// Set up label
|
||||
this.labelCell = new OO.ui.LabelWidget( {
|
||||
classes: [ 'mw-widgets-rowWidget-label' ]
|
||||
} );
|
||||
|
||||
// Set up delete button
|
||||
if ( this.model.getRowProperties().isDeletable ) {
|
||||
this.deleteButton = new OO.ui.ButtonWidget( {
|
||||
icon: 'trash',
|
||||
classes: [ 'mw-widgets-rowWidget-delete-button' ],
|
||||
flags: 'destructive',
|
||||
title: mw.msg( 'mw-widgets-table-row-delete' )
|
||||
} );
|
||||
}
|
||||
|
||||
// Events
|
||||
this.model.connect( this, {
|
||||
valueChange: 'onValueChange',
|
||||
insertCell: 'onInsertCell',
|
||||
removeCell: 'onRemoveCell',
|
||||
clear: 'onClear',
|
||||
labelUpdate: 'onLabelUpdate'
|
||||
} );
|
||||
|
||||
this.aggregate( {
|
||||
change: 'cellChange'
|
||||
} );
|
||||
|
||||
this.connect( this, {
|
||||
cellChange: 'onCellChange'
|
||||
} );
|
||||
|
||||
if ( this.model.getRowProperties().isDeletable ) {
|
||||
this.deleteButton.connect( this, {
|
||||
click: 'onDeleteButtonClick'
|
||||
} );
|
||||
}
|
||||
|
||||
// Initialization
|
||||
this.$element.addClass( 'mw-widgets-rowWidget' );
|
||||
|
||||
this.$element.append(
|
||||
this.labelCell.$element,
|
||||
this.$group
|
||||
);
|
||||
|
||||
if ( this.model.getRowProperties().isDeletable ) {
|
||||
this.$element.append( this.deleteButton.$element );
|
||||
}
|
||||
|
||||
this.setLabel( this.model.getRowProperties().label );
|
||||
|
||||
this.model.setupRow();
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
||||
OO.inheritClass( mw.widgets.RowWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.widgets.RowWidget, OO.ui.mixin.GroupElement );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event inputChange
|
||||
*
|
||||
* Change when an input contained within the row is updated
|
||||
*
|
||||
* @param {number} index The index of the cell that changed
|
||||
* @param {string} value The new value of the cell
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event deleteButtonClick
|
||||
*
|
||||
* Fired when the delete button for the row is pressed
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.addItems = function ( items, index ) {
|
||||
var i, len;
|
||||
|
||||
OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
|
||||
|
||||
for ( i = index, len = items.length; i < len; i++ ) {
|
||||
items[ i ].setData( i );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.removeItems = function ( items ) {
|
||||
var i, len, cells;
|
||||
|
||||
OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
|
||||
|
||||
cells = this.getItems();
|
||||
for ( i = 0, len = cells.length; i < len; i++ ) {
|
||||
cells[ i ].setData( i );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the row index
|
||||
*
|
||||
* @return {number} The row index
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.getIndex = function () {
|
||||
return this.model.getRowProperties().index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the row index
|
||||
*
|
||||
* @param {number} index The new index
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.setIndex = function ( index ) {
|
||||
this.model.setIndex( index );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the label displayed on the row. If no custom label is set, the
|
||||
* row index is used instead.
|
||||
*
|
||||
* @return {string} The row label
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.getLabel = function () {
|
||||
var props = this.model.getRowProperties();
|
||||
|
||||
if ( props.label === null ) {
|
||||
return '';
|
||||
} else if ( !props.label ) {
|
||||
return props.index.toString();
|
||||
} else {
|
||||
return props.label;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the label to be displayed on the widget.
|
||||
*
|
||||
* @param {string} label The new label
|
||||
* @fires labelUpdate
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.setLabel = function ( label ) {
|
||||
this.model.setLabel( label );
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a particular cell
|
||||
*
|
||||
* @param {number} index The cell index
|
||||
* @param {string} value The new value
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.setValue = function ( index, value ) {
|
||||
this.model.setValue( index, value );
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a cell at a specified index
|
||||
*
|
||||
* @param {string} data The cell data
|
||||
* @param {number} index The index to insert the cell at
|
||||
* @param {string} key A key for easy cell selection
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.insertCell = function ( data, index, key ) {
|
||||
this.model.insertCell( data, index, key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a column at a specified index
|
||||
*
|
||||
* @param {number} index The index to removeColumn
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.removeCell = function ( index ) {
|
||||
this.model.removeCell( index );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the field values
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.clear = function () {
|
||||
this.model.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model value changes
|
||||
*
|
||||
* @param {number} index The column index of the updated cell
|
||||
* @param {number} value The new value
|
||||
*
|
||||
* @fires inputChange
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onValueChange = function ( index, value ) {
|
||||
this.getItems()[ index ].setValue( value );
|
||||
this.emit( 'inputChange', index, value );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model cell insertions
|
||||
*
|
||||
* @param {string} data The initial data
|
||||
* @param {number} index The index in which to insert the new cell
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onInsertCell = function ( data, index ) {
|
||||
this.addItems( [
|
||||
new OO.ui.TextInputWidget( {
|
||||
data: index,
|
||||
value: data,
|
||||
validate: this.model.getValidationPattern()
|
||||
} )
|
||||
], index );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model cell removals
|
||||
*
|
||||
* @param {number} index The removed cell index
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onRemoveCell = function ( index ) {
|
||||
this.removeItems( [ index ] );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle clear requests
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onClear = function () {
|
||||
var i, len,
|
||||
cells = this.getItems();
|
||||
|
||||
for ( i = 0, len = cells.length; i < len; i++ ) {
|
||||
cells[ i ].setValue( '' );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update model label changes
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onLabelUpdate = function () {
|
||||
this.labelCell.setLabel( this.getLabel() );
|
||||
};
|
||||
|
||||
/**
|
||||
* React to cell input change
|
||||
*
|
||||
* @private
|
||||
* @param {OO.ui.TextInputWidget} input The input that fired the event
|
||||
* @param {string} value The value of the input
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onCellChange = function ( input, value ) {
|
||||
// FIXME: The table itself should know if it contains invalid data
|
||||
// in order to pass form state to the dialog when it asks if the Apply
|
||||
// button should be enabled or not. This probably requires the table
|
||||
// and each individual row to handle validation through an array of promises
|
||||
// fed from the cells within.
|
||||
// Right now, the table can't know if it's valid or not because the events
|
||||
// don't get passed through.
|
||||
var self = this;
|
||||
input.getValidity().done( function () {
|
||||
self.model.setValue( input.getData(), value );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle delete button clicks
|
||||
*
|
||||
* @private
|
||||
* @fires deleteButtonClick
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.onDeleteButtonClick = function () {
|
||||
this.emit( 'deleteButtonClick' );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.RowWidget.prototype.setDisabled = function ( disabled ) {
|
||||
// Parent method
|
||||
mw.widgets.RowWidget.super.prototype.setDisabled.call( this, disabled );
|
||||
|
||||
if ( !this.items ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deleteButton.setDisabled( disabled );
|
||||
|
||||
this.getItems().forEach( function ( cell ) {
|
||||
cell.setDisabled( disabled );
|
||||
} );
|
||||
};
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
/*!
|
||||
* MediaWiki Widgets RowWidgetModel class
|
||||
*
|
||||
* @license The MIT License (MIT); see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* RowWidget model.
|
||||
*
|
||||
* @class
|
||||
* @mixins OO.EventEmitter
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} [config] Configuration options
|
||||
* @cfg {Array} [data] An array containing all values of the row
|
||||
* @cfg {Array} [keys] An array of keys for easy cell selection
|
||||
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
|
||||
* @cfg {string} [label=''] Row label. Defaults to empty string.
|
||||
* @cfg {boolean} [showLabel=true] Show row label. Defaults to true.
|
||||
* @cfg {boolean} [deletable=true] Allow row to be deleted. Defaults to true.
|
||||
*/
|
||||
mw.widgets.RowWidgetModel = function MwWidgetsRowWidgetModel( config ) {
|
||||
config = config || {};
|
||||
|
||||
// Mixin constructors
|
||||
OO.EventEmitter.call( this, config );
|
||||
|
||||
this.data = config.data || [];
|
||||
this.validate = config.validate;
|
||||
this.index = ( config.index !== undefined ) ? config.index : -1;
|
||||
this.label = ( config.label !== undefined ) ? config.label : '';
|
||||
this.showLabel = ( config.showLabel !== undefined ) ? !!config.showLabel : true;
|
||||
this.isDeletable = ( config.deletable !== undefined ) ? !!config.deletable : true;
|
||||
|
||||
this.initializeProps( config.keys );
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
||||
OO.mixinClass( mw.widgets.RowWidgetModel, OO.EventEmitter );
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event valueChange
|
||||
*
|
||||
* Fired when a value inside the row has changed.
|
||||
*
|
||||
* @param {number} index The column index of the updated cell
|
||||
* @param {number} value The new value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event insertCell
|
||||
*
|
||||
* Fired when a new cell is inserted into the row.
|
||||
*
|
||||
* @param {Array} data The initial data
|
||||
* @param {number} index The index in which to insert the new cell
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event removeCell
|
||||
*
|
||||
* Fired when a cell is removed from the row.
|
||||
*
|
||||
* @param {number} index The removed cell index
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event clear
|
||||
*
|
||||
* Fired when the row is cleared
|
||||
*
|
||||
* @param {boolean} clear Clear cell properties
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event labelUpdate
|
||||
*
|
||||
* Fired when the row label might need to be updated
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Initializes and ensures the proper creation of the cell property array.
|
||||
* If data exceeds the number of cells given, new ones will be created.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} props The initial cell props
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.initializeProps = function ( props ) {
|
||||
var i, len;
|
||||
|
||||
this.cells = [];
|
||||
|
||||
if ( Array.isArray( props ) ) {
|
||||
for ( i = 0, len = props.length; i < len; i++ ) {
|
||||
this.cells.push( {
|
||||
index: i,
|
||||
key: props[ i ]
|
||||
} );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers the initialization process and builds the initial row.
|
||||
*
|
||||
* @fires insertCell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.setupRow = function () {
|
||||
this.verifyData();
|
||||
this.buildRow();
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies if the table data is complete and synced with
|
||||
* cell properties, and adds empty strings as cell data if
|
||||
* cells are missing
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.verifyData = function () {
|
||||
var i, len;
|
||||
|
||||
for ( i = 0, len = this.cells.length; i < len; i++ ) {
|
||||
if ( this.data[ i ] === undefined ) {
|
||||
this.data.push( '' );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build initial row
|
||||
*
|
||||
* @private
|
||||
* @fires insertCell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.buildRow = function () {
|
||||
var i, len;
|
||||
|
||||
for ( i = 0, len = this.cells.length; i < len; i++ ) {
|
||||
this.emit( 'insertCell', this.data[ i ], i );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the entire row with new data
|
||||
*
|
||||
* @private
|
||||
* @fires insertCell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.refreshRow = function () {
|
||||
// TODO: Clear existing table
|
||||
|
||||
this.buildRow();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a particular cell
|
||||
*
|
||||
* @param {number|string} handle The index or key of the cell
|
||||
* @param {Mixed} value The new value
|
||||
* @fires valueChange
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.setValue = function ( handle, value ) {
|
||||
var index;
|
||||
|
||||
if ( typeof handle === 'number' ) {
|
||||
index = handle;
|
||||
} else if ( typeof handle === 'string' ) {
|
||||
index = this.getCellProperties( handle ).index;
|
||||
}
|
||||
|
||||
if ( typeof index === 'number' && this.data[ index ] !== undefined &&
|
||||
this.data[ index ] !== value ) {
|
||||
|
||||
this.data[ index ] = value;
|
||||
this.emit( 'valueChange', index, value );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the row data
|
||||
*
|
||||
* @param {Array} data The new row data
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.setData = function ( data ) {
|
||||
if ( Array.isArray( data ) ) {
|
||||
this.data = data;
|
||||
|
||||
this.verifyData();
|
||||
this.refreshRow();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the row index
|
||||
*
|
||||
* @param {number} index The new row index
|
||||
* @fires labelUpdate
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.setIndex = function ( index ) {
|
||||
this.index = index;
|
||||
this.emit( 'labelUpdate' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the row label
|
||||
*
|
||||
* @param {number} label The new row label
|
||||
* @fires labelUpdate
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.setLabel = function ( label ) {
|
||||
this.label = label;
|
||||
this.emit( 'labelUpdate' );
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts a row into the table. If the row isn't added at the end of the table,
|
||||
* all the following data will be shifted back one row.
|
||||
*
|
||||
* @param {number|string} [data] The data to insert to the cell.
|
||||
* @param {number} [index] The index in which to insert the new cell.
|
||||
* If unset or set to null, the cell will be added at the end of the row.
|
||||
* @param {string} [key] A key to quickly select this cell.
|
||||
* If unset or set to null, no key will be set.
|
||||
* @fires insertCell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.insertCell = function ( data, index, key ) {
|
||||
var insertIndex = ( typeof index === 'number' ) ? index : this.cells.length,
|
||||
insertData, i, len;
|
||||
|
||||
// Add the new cell metadata
|
||||
this.cells.splice( insertIndex, 0, {
|
||||
index: insertIndex,
|
||||
key: key || undefined
|
||||
} );
|
||||
|
||||
// Add the new row data
|
||||
insertData = ( typeof data === 'string' || typeof data === 'number' ) ? data : '';
|
||||
this.data.splice( insertIndex, 0, insertData );
|
||||
|
||||
// Update all indexes in following cells
|
||||
for ( i = insertIndex + 1, len = this.cells.length; i < len; i++ ) {
|
||||
this.cells[ i ].index++;
|
||||
}
|
||||
|
||||
this.emit( 'insertCell', data, insertIndex );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a cell from the table. If the cell removed isn't at the end of the table,
|
||||
* all the following cells will be shifted back one cell.
|
||||
*
|
||||
* @param {number|string} handle The key or numerical index of the cell to remove
|
||||
* @fires removeCell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.removeCell = function ( handle ) {
|
||||
var cellProps = this.getCellProperties( handle ),
|
||||
i, len;
|
||||
|
||||
// Exit early if the row couldn't be found
|
||||
if ( cellProps === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cells.splice( cellProps.index, 1 );
|
||||
this.data.splice( cellProps.index, 1 );
|
||||
|
||||
// Update all indexes in following cells
|
||||
for ( i = cellProps.index, len = this.cells.length; i < len; i++ ) {
|
||||
this.cells[ i ].index--;
|
||||
}
|
||||
|
||||
this.emit( 'removeCell', cellProps.index );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the row data
|
||||
*
|
||||
* @fires clear
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.clear = function () {
|
||||
this.data = [];
|
||||
this.verifyData();
|
||||
|
||||
this.emit( 'clear', false );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the row data, as well as all cell properties
|
||||
*
|
||||
* @fires clear
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.clearWithProperties = function () {
|
||||
this.data = [];
|
||||
this.cells = [];
|
||||
|
||||
this.emit( 'clear', true );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the validation pattern to test cells against
|
||||
*
|
||||
* @return {RegExp|Function|string}
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.getValidationPattern = function () {
|
||||
return this.validate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all row properties
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.getRowProperties = function () {
|
||||
return {
|
||||
index: this.index,
|
||||
label: this.label,
|
||||
showLabel: this.showLabel,
|
||||
isDeletable: this.isDeletable
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of a given cell
|
||||
*
|
||||
* @param {string|number} handle The key (or numeric index) of the cell
|
||||
* @return {Object|null} An object containing the `key` and `index` properties of the cell.
|
||||
* Returns `null` if the cell can't be found.
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.getCellProperties = function ( handle ) {
|
||||
var cell = null,
|
||||
i, len;
|
||||
|
||||
if ( typeof handle === 'string' ) {
|
||||
for ( i = 0, len = this.cells.length; i < len; i++ ) {
|
||||
if ( this.cells[ i ].key === handle ) {
|
||||
cell = this.cells[ i ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ( typeof handle === 'number' ) {
|
||||
if ( handle < this.cells.length ) {
|
||||
cell = this.cells[ handle ];
|
||||
}
|
||||
}
|
||||
|
||||
return cell;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of all cells
|
||||
*
|
||||
* @return {Array} An array of objects containing `key` and `index` properties for each cell
|
||||
*/
|
||||
mw.widgets.RowWidgetModel.prototype.getAllCellProperties = function () {
|
||||
return this.cells.slice();
|
||||
};
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
.mw-widgets-tableWidget > .mw-widgets-tableWidget-rows {
|
||||
float: left;
|
||||
clear: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mw-widgets-tableWidget.mw-widgets-tableWidget-no-labels .mw-widgets-rowWidget-label {
|
||||
display: none;
|
||||
}
|
||||
587
resources/src/mediawiki.widgets/Table/mw.widgets.TableWidget.js
Normal file
587
resources/src/mediawiki.widgets/Table/mw.widgets.TableWidget.js
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
/**
|
||||
* A TableWidget groups {@link mw.widgets.RowWidget row widgets} together to form a bidimensional
|
||||
* grid of text inputs.
|
||||
*
|
||||
* @class
|
||||
* @extends OO.ui.Widget
|
||||
* @mixins OO.ui.mixin.GroupElement
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} [config] Configuration options
|
||||
* @cfg {Array} [rows] An array of objects containing `key` and `label` properties for every row
|
||||
* @cfg {Array} [cols] An array of objects containing `key` and `label` properties for every column
|
||||
* @cfg {Array} [data] An array containing all values of the table
|
||||
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
|
||||
* @cfg {boolean} [showHeaders=true] Whether or not to show table headers. Defaults to true.
|
||||
* @cfg {boolean} [showRowLabels=true] Whether or not to show row labels. Defaults to true.
|
||||
* @cfg {boolean} [allowRowInsertion=true] Whether or not to enable row insertion. Defaults to true.
|
||||
* @cfg {boolean} [allowRowDeletion=true] Allow row deletion. Defaults to true.
|
||||
*/
|
||||
mw.widgets.TableWidget = function MwWidgetsTableWidget( config ) {
|
||||
var headerRowItems = [],
|
||||
insertionRowItems = [],
|
||||
columnProps, prop, i, len;
|
||||
|
||||
// Configuration initialization
|
||||
config = config || {};
|
||||
|
||||
// Parent constructor
|
||||
mw.widgets.TableWidget.super.call( this, config );
|
||||
|
||||
// Mixin constructors
|
||||
OO.ui.mixin.GroupElement.call( this, config );
|
||||
|
||||
// Set up model
|
||||
this.model = new mw.widgets.TableWidgetModel( config );
|
||||
|
||||
// Properties
|
||||
this.listeningToInsertionRowChanges = true;
|
||||
|
||||
// Set up group element
|
||||
this.setGroupElement(
|
||||
$( '<div>' )
|
||||
.addClass( 'mw-widgets-tableWidget-rows' )
|
||||
);
|
||||
|
||||
// Set up static rows
|
||||
columnProps = this.model.getAllColumnProperties();
|
||||
|
||||
if ( this.model.getTableProperties().showHeaders ) {
|
||||
this.headerRow = new mw.widgets.RowWidget( {
|
||||
deletable: false,
|
||||
label: null
|
||||
} );
|
||||
|
||||
for ( i = 0, len = columnProps.length; i < len; i++ ) {
|
||||
prop = columnProps[ i ];
|
||||
headerRowItems.push( new OO.ui.TextInputWidget( {
|
||||
value: prop.label ? prop.label : ( prop.key ? prop.key : prop.index ),
|
||||
// TODO: Allow editing of fields
|
||||
disabled: true
|
||||
} ) );
|
||||
}
|
||||
|
||||
this.headerRow.addItems( headerRowItems );
|
||||
}
|
||||
|
||||
if ( this.model.getTableProperties().allowRowInsertion ) {
|
||||
this.insertionRow = new mw.widgets.RowWidget( {
|
||||
classes: 'mw-widgets-rowWidget-insertionRow',
|
||||
deletable: false,
|
||||
label: null
|
||||
} );
|
||||
|
||||
for ( i = 0, len = columnProps.length; i < len; i++ ) {
|
||||
insertionRowItems.push( new OO.ui.TextInputWidget( {
|
||||
data: columnProps[ i ].key ? columnProps[ i ].key : columnProps[ i ].index,
|
||||
disabled: this.isDisabled()
|
||||
} ) );
|
||||
}
|
||||
|
||||
this.insertionRow.addItems( insertionRowItems );
|
||||
}
|
||||
|
||||
// Set up initial rows
|
||||
if ( Array.isArray( config.items ) ) {
|
||||
this.addItems( config.items );
|
||||
}
|
||||
|
||||
// Events
|
||||
this.model.connect( this, {
|
||||
valueChange: 'onValueChange',
|
||||
insertRow: 'onInsertRow',
|
||||
insertColumn: 'onInsertColumn',
|
||||
removeRow: 'onRemoveRow',
|
||||
removeColumn: 'onRemoveColumn',
|
||||
clear: 'onClear'
|
||||
} );
|
||||
|
||||
this.aggregate( {
|
||||
inputChange: 'rowInputChange',
|
||||
deleteButtonClick: 'rowDeleteButtonClick'
|
||||
} );
|
||||
|
||||
this.connect( this, {
|
||||
rowInputChange: 'onRowInputChange',
|
||||
rowDeleteButtonClick: 'onRowDeleteButtonClick'
|
||||
} );
|
||||
|
||||
if ( this.model.getTableProperties().allowRowInsertion ) {
|
||||
this.insertionRow.connect( this, {
|
||||
inputChange: 'onInsertionRowInputChange'
|
||||
} );
|
||||
}
|
||||
|
||||
// Initialization
|
||||
this.$element.addClass( 'mw-widgets-tableWidget' );
|
||||
|
||||
if ( this.model.getTableProperties().showHeaders ) {
|
||||
this.$element.append( this.headerRow.$element );
|
||||
}
|
||||
this.$element.append( this.$group );
|
||||
if ( this.model.getTableProperties().allowRowInsertion ) {
|
||||
this.$element.append( this.insertionRow.$element );
|
||||
}
|
||||
|
||||
this.$element.toggleClass(
|
||||
'mw-widgets-tableWidget-no-labels',
|
||||
!this.model.getTableProperties().showRowLabels
|
||||
);
|
||||
|
||||
this.model.setupTable();
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
||||
OO.inheritClass( mw.widgets.TableWidget, OO.ui.Widget );
|
||||
OO.mixinClass( mw.widgets.TableWidget, OO.ui.mixin.GroupElement );
|
||||
|
||||
/* Static Properties */
|
||||
mw.widgets.TableWidget.static.patterns = {
|
||||
validate: /^[0-9]+(\.[0-9]+)?$/,
|
||||
filter: /[0-9]+(\.[0-9]+)?/
|
||||
};
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event change
|
||||
*
|
||||
* Change when the data within the table has been updated.
|
||||
*
|
||||
* @param {number} rowIndex The index of the row that changed
|
||||
* @param {string} rowKey The key of the row that changed, or `undefined` if it doesn't exist
|
||||
* @param {number} columnIndex The index of the column that changed
|
||||
* @param {string} columnKey The key of the column that changed, or `undefined` if it doesn't exist
|
||||
* @param {string} value The new value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event removeRow
|
||||
*
|
||||
* Fires when a row is removed from the table
|
||||
*
|
||||
* @param {number} index The index of the row being deleted
|
||||
* @param {string} key The key of the row being deleted
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Set the value of a particular cell
|
||||
*
|
||||
* @param {number|string} row The row containing the cell to edit. Can be either
|
||||
* the row index or string key if one has been set for the row.
|
||||
* @param {number|string} col The column containing the cell to edit. Can be either
|
||||
* the column index or string key if one has been set for the column.
|
||||
* @param {Mixed} value The new value
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.setValue = function ( row, col, value ) {
|
||||
this.model.setValue( row, col, value );
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the table data
|
||||
*
|
||||
* @param {Array} data The new table data
|
||||
* @return {boolean} The data has been successfully changed
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.setData = function ( data ) {
|
||||
if ( !Array.isArray( data ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.model.setData( data );
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts a row into the table. If the row isn't added at the end of the table,
|
||||
* all the following data will be shifted back one row.
|
||||
*
|
||||
* @param {Array} [data] The data to insert to the row.
|
||||
* @param {number} [index] The index in which to insert the new row.
|
||||
* If unset or set to null, the row will be added at the end of the table.
|
||||
* @param {string} [key] A key to quickly select this row.
|
||||
* If unset or set to null, no key will be set.
|
||||
* @param {string} [label] A label to display next to the row.
|
||||
* If unset or set to null, the key will be used if there is one.
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.insertRow = function ( data, index, key, label ) {
|
||||
this.model.insertRow( data, index, key, label );
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts a column into the table. If the column isn't added at the end of the table,
|
||||
* all the following data will be shifted back one column.
|
||||
*
|
||||
* @param {Array} [data] The data to insert to the column.
|
||||
* @param {number} [index] The index in which to insert the new column.
|
||||
* If unset or set to null, the column will be added at the end of the table.
|
||||
* @param {string} [key] A key to quickly select this column.
|
||||
* If unset or set to null, no key will be set.
|
||||
* @param {string} [label] A label to display next to the column.
|
||||
* If unset or set to null, the key will be used if there is one.
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.insertColumn = function ( data, index, key, label ) {
|
||||
this.model.insertColumn( data, index, key, label );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a row from the table. If the row removed isn't at the end of the table,
|
||||
* all the following rows will be shifted back one row.
|
||||
*
|
||||
* @param {number|string} key The key or numerical index of the row to remove.
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.removeRow = function ( key ) {
|
||||
this.model.removeRow( key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a column from the table. If the column removed isn't at the end of the table,
|
||||
* all the following columns will be shifted back one column.
|
||||
*
|
||||
* @param {number|string} key The key or numerical index of the column to remove.
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.removeColumn = function ( key ) {
|
||||
this.model.removeColumn( key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears all values from the table, without wiping any row or column properties.
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.clear = function () {
|
||||
this.model.clear();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the table data, as well as all row and column properties
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.clearWithProperties = function () {
|
||||
this.model.clearWithProperties();
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter cell input once it is changed
|
||||
*
|
||||
* @param {string} value The input value
|
||||
* @return {string} The filtered input
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.filterCellInput = function ( value ) {
|
||||
var matches = value.match( mw.widgets.TableWidget.static.patterns.filter );
|
||||
return ( Array.isArray( matches ) ) ? matches[ 0 ] : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.addItems = function ( items, index ) {
|
||||
var i, len;
|
||||
|
||||
OO.ui.mixin.GroupElement.prototype.addItems.call( this, items, index );
|
||||
|
||||
for ( i = index, len = items.length; i < len; i++ ) {
|
||||
items[ i ].setIndex( i );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.removeItems = function ( items ) {
|
||||
var i, len, rows;
|
||||
|
||||
OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items );
|
||||
|
||||
rows = this.getItems();
|
||||
for ( i = 0, len = rows.length; i < len; i++ ) {
|
||||
rows[ i ].setIndex( i );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model value changes
|
||||
*
|
||||
* @private
|
||||
* @param {number} row The row index of the changed cell
|
||||
* @param {number} col The column index of the changed cell
|
||||
* @param {Mixed} value The new value
|
||||
* @fires change
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onValueChange = function ( row, col, value ) {
|
||||
var rowProps = this.model.getRowProperties( row ),
|
||||
colProps = this.model.getColumnProperties( col );
|
||||
|
||||
this.getItems()[ row ].setValue( col, value );
|
||||
|
||||
this.emit( 'change', row, rowProps.key, col, colProps.key, value );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model row insertions
|
||||
*
|
||||
* @private
|
||||
* @param {Array} data The initial data
|
||||
* @param {number} index The index in which the new row was inserted
|
||||
* @param {string} key The row key
|
||||
* @param {string} label The row label
|
||||
* @fires change
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onInsertRow = function ( data, index, key, label ) {
|
||||
var colProps = this.model.getAllColumnProperties(),
|
||||
keys = [],
|
||||
newRow, i, len;
|
||||
|
||||
for ( i = 0, len = colProps.length; i < len; i++ ) {
|
||||
keys.push( ( colProps[ i ].key ) ? colProps[ i ].key : i );
|
||||
}
|
||||
|
||||
newRow = new mw.widgets.RowWidget( {
|
||||
data: data,
|
||||
keys: keys,
|
||||
validate: this.model.getValidationPattern(),
|
||||
label: label,
|
||||
showLabel: this.model.getTableProperties().showRowLabels,
|
||||
deletable: this.model.getTableProperties().allowRowDeletion
|
||||
} );
|
||||
|
||||
newRow.setDisabled( this.isDisabled() );
|
||||
|
||||
// TODO: Handle index parameter. Right now all new rows are inserted at the end
|
||||
this.addItems( [ newRow ] );
|
||||
|
||||
// If this is the first data being added, refresh headers and insertion row
|
||||
if ( this.model.getAllRowProperties().length === 1 ) {
|
||||
this.refreshTableMarginals();
|
||||
}
|
||||
|
||||
for ( i = 0, len = data.length; i < len; i++ ) {
|
||||
this.emit( 'change', index, key, i, colProps[ i ].key, data[ i ] );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model column insertions
|
||||
*
|
||||
* @private
|
||||
* @param {Array} data The initial data
|
||||
* @param {number} index The index in which to insert the new column
|
||||
* @param {string} key The row key
|
||||
* @param {string} label The row label
|
||||
*
|
||||
* @fires change
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onInsertColumn = function ( data, index, key, label ) {
|
||||
var tableProps = this.model.getTableProperties(),
|
||||
items = this.getItems(),
|
||||
rowProps = this.model.getAllRowProperties(),
|
||||
i, len;
|
||||
|
||||
for ( i = 0, len = items.length; i < len; i++ ) {
|
||||
items[ i ].insertCell( data[ i ], index, key );
|
||||
this.emit( 'change', i, rowProps[ i ].key, index, key, data[ i ] );
|
||||
}
|
||||
|
||||
if ( tableProps.showHeaders ) {
|
||||
this.headerRow.addItems( [
|
||||
new OO.ui.TextInputWidget( {
|
||||
value: label || key || index,
|
||||
// TODO: Allow editing of fields
|
||||
disabled: true
|
||||
} )
|
||||
] );
|
||||
}
|
||||
|
||||
if ( tableProps.handleRowInsertion ) {
|
||||
this.insertionRow.addItems( [
|
||||
new OO.ui.TextInputWidget( {
|
||||
validate: this.model.getValidationPattern(),
|
||||
disabled: this.isDisabled()
|
||||
} )
|
||||
] );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model row removals
|
||||
*
|
||||
* @private
|
||||
* @param {number} index The removed row index
|
||||
* @param {string} key The removed row key
|
||||
* @fires removeRow
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onRemoveRow = function ( index, key ) {
|
||||
this.removeItems( [ this.getItems()[ index ] ] );
|
||||
this.emit( 'removeRow', index, key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model column removals
|
||||
*
|
||||
* @private
|
||||
* @param {number} index The removed column index
|
||||
* @param {string} key The removed column key
|
||||
* @fires removeColumn
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onRemoveColumn = function ( index, key ) {
|
||||
var i, items = this.getItems();
|
||||
|
||||
for ( i = 0; i < items.length; i++ ) {
|
||||
items[ i ].removeCell( index );
|
||||
}
|
||||
|
||||
this.emit( 'removeColumn', index, key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model table clears
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} withProperties Clear row/column properties
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onClear = function ( withProperties ) {
|
||||
var i, len, rows;
|
||||
|
||||
if ( withProperties ) {
|
||||
this.removeItems( this.getItems() );
|
||||
} else {
|
||||
rows = this.getItems();
|
||||
|
||||
for ( i = 0, len = rows.length; i < len; i++ ) {
|
||||
rows[ i ].clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* React to input changes bubbled up from event aggregation
|
||||
*
|
||||
* @private
|
||||
* @param {mw.widgets.RowWidget} row The row that changed
|
||||
* @param {number} colIndex The column index of the cell that changed
|
||||
* @param {string} value The new value of the input
|
||||
* @fires change
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onRowInputChange = function ( row, colIndex, value ) {
|
||||
var items = this.getItems(),
|
||||
i, len, rowIndex;
|
||||
|
||||
for ( i = 0, len = items.length; i < len; i++ ) {
|
||||
if ( row === items[ i ] ) {
|
||||
rowIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.model.setValue( rowIndex, colIndex, value );
|
||||
};
|
||||
|
||||
/**
|
||||
* React to new row input changes
|
||||
*
|
||||
* @private
|
||||
* @param {number} colIndex The column index of the input that fired the change
|
||||
* @param {string} value The new row value
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onInsertionRowInputChange = function ( colIndex, value ) {
|
||||
var insertionRowItems = this.insertionRow.getItems(),
|
||||
newRowData = [],
|
||||
i, len, lastRow;
|
||||
|
||||
if ( this.listeningToInsertionRowChanges ) {
|
||||
for ( i = 0, len = insertionRowItems.length; i < len; i++ ) {
|
||||
if ( i === colIndex ) {
|
||||
newRowData.push( value );
|
||||
} else {
|
||||
newRowData.push( '' );
|
||||
}
|
||||
}
|
||||
|
||||
this.insertRow( newRowData );
|
||||
|
||||
// Focus newly inserted row
|
||||
lastRow = this.getItems().slice( -1 )[ 0 ];
|
||||
lastRow.getItems()[ colIndex ].focus();
|
||||
|
||||
// Reset insertion row
|
||||
this.listeningToInsertionRowChanges = false;
|
||||
this.insertionRow.clear();
|
||||
this.listeningToInsertionRowChanges = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle row deletion input
|
||||
*
|
||||
* @private
|
||||
* @param {mw.widgets.RowWidget} row The row that asked for the deletion
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.onRowDeleteButtonClick = function ( row ) {
|
||||
var items = this.getItems(),
|
||||
i = -1,
|
||||
len;
|
||||
|
||||
for ( i = 0, len = items.length; i < len; i++ ) {
|
||||
if ( items[ i ] === row ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.removeRow( i );
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.setDisabled = function ( disabled ) {
|
||||
// Parent method
|
||||
mw.widgets.TableWidget.super.prototype.setDisabled.call( this, disabled );
|
||||
|
||||
if ( !this.items ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getItems().forEach( function ( row ) {
|
||||
row.setDisabled( disabled );
|
||||
} );
|
||||
|
||||
this.insertionRow.getItems().forEach( function ( row ) {
|
||||
row.setDisabled( disabled );
|
||||
} );
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh table header and insertion row
|
||||
*/
|
||||
mw.widgets.TableWidget.prototype.refreshTableMarginals = function () {
|
||||
var tableProps = this.model.getTableProperties(),
|
||||
columnProps = this.model.getAllColumnProperties(),
|
||||
rowItems,
|
||||
i, len;
|
||||
|
||||
if ( tableProps.showHeaders ) {
|
||||
this.headerRow.removeItems( this.headerRow.getItems() );
|
||||
rowItems = [];
|
||||
|
||||
for ( i = 0, len = columnProps.length; i < len; i++ ) {
|
||||
rowItems.push( new OO.ui.TextInputWidget( {
|
||||
value: columnProps[ i ].key ? columnProps[ i ].key : columnProps[ i ].index,
|
||||
// TODO: Allow editing of fields
|
||||
disabled: true
|
||||
} ) );
|
||||
}
|
||||
|
||||
this.headerRow.addItems( rowItems );
|
||||
}
|
||||
|
||||
if ( tableProps.allowRowInsertion ) {
|
||||
this.insertionRow.clear();
|
||||
this.insertionRow.removeItems( this.insertionRow.getItems() );
|
||||
|
||||
for ( i = 0, len = columnProps.length; i < len; i++ ) {
|
||||
this.insertionRow.insertCell( '', columnProps[ i ].index, columnProps[ i ].key );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,519 @@
|
|||
/*!
|
||||
* MediaWiki Widgets TableWidgetModel class.
|
||||
*
|
||||
* @license The MIT License (MIT); see LICENSE.txt
|
||||
*/
|
||||
|
||||
/**
|
||||
* TableWidget model.
|
||||
*
|
||||
* @class
|
||||
* @mixins OO.EventEmitter
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} [config] Configuration options
|
||||
* @cfg {Array} [rows] An array of objects containing `key` and `label` properties for every row
|
||||
* @cfg {Array} [cols] An array of objects containing `key` and `label` properties for every column
|
||||
* @cfg {Array} [data] An array containing all values of the table
|
||||
* @cfg {RegExp|Function|string} [validate] Validation pattern to apply on every cell
|
||||
* @cfg {boolean} [showHeaders=true] Show table header row. Defaults to true.
|
||||
* @cfg {boolean} [showRowLabels=true] Show row labels. Defaults to true.
|
||||
* @cfg {boolean} [allowRowInsertion=true] Allow row insertion. Defaults to true.
|
||||
* @cfg {boolean} [allowRowDeletion=true] Allow row deletion. Defaults to true.
|
||||
*/
|
||||
mw.widgets.TableWidgetModel = function MwWidgetsTableWidgetModel( config ) {
|
||||
config = config || {};
|
||||
|
||||
// Mixin constructors
|
||||
OO.EventEmitter.call( this, config );
|
||||
|
||||
this.data = config.data || [];
|
||||
this.validate = config.validate;
|
||||
this.showHeaders = ( config.showHeaders !== undefined ) ? !!config.showHeaders : true;
|
||||
this.showRowLabels = ( config.showRowLabels !== undefined ) ? !!config.showRowLabels : true;
|
||||
this.allowRowInsertion = ( config.allowRowInsertion !== undefined ) ?
|
||||
!!config.allowRowInsertion : true;
|
||||
this.allowRowDeletion = ( config.allowRowDeletion !== undefined ) ?
|
||||
!!config.allowRowDeletion : true;
|
||||
|
||||
this.initializeProps( config.rows, config.cols );
|
||||
};
|
||||
|
||||
/* Inheritance */
|
||||
|
||||
OO.mixinClass( mw.widgets.TableWidgetModel, OO.EventEmitter );
|
||||
|
||||
/* Static Methods */
|
||||
|
||||
/**
|
||||
* Get an entry from a props table
|
||||
*
|
||||
* @static
|
||||
* @private
|
||||
* @param {string|number} handle The key (or numeric index) of the row/column
|
||||
* @param {Array} table Props table
|
||||
* @return {Object|null} An object containing the `key`, `index` and `label`
|
||||
* properties of the row/column. Returns `null` if the row/column can't be found.
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.static.getEntryFromPropsTable = function ( handle, table ) {
|
||||
var row = null,
|
||||
i, len;
|
||||
|
||||
if ( typeof handle === 'string' ) {
|
||||
for ( i = 0, len = table.length; i < len; i++ ) {
|
||||
if ( table[ i ].key === handle ) {
|
||||
row = table[ i ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ( typeof handle === 'number' ) {
|
||||
if ( handle < table.length ) {
|
||||
row = table[ handle ];
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
/* Events */
|
||||
|
||||
/**
|
||||
* @event valueChange
|
||||
*
|
||||
* Fired when a value inside the table has changed.
|
||||
*
|
||||
* @param {number} row The row index of the updated cell
|
||||
* @param {number} column The column index of the updated cell
|
||||
* @param {Mixed} value The new value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event insertRow
|
||||
*
|
||||
* Fired when a new row is inserted into the table.
|
||||
*
|
||||
* @param {Array} data The initial data
|
||||
* @param {number} index The index in which to insert the new row
|
||||
* @param {string} key The row key
|
||||
* @param {string} label The row label
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event insertColumn
|
||||
*
|
||||
* Fired when a new row is inserted into the table.
|
||||
*
|
||||
* @param {Array} data The initial data
|
||||
* @param {number} index The index in which to insert the new column
|
||||
* @param {string} key The column key
|
||||
* @param {string} label The column label
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event removeRow
|
||||
*
|
||||
* Fired when a row is removed from the table.
|
||||
*
|
||||
* @param {number} index The removed row index
|
||||
* @param {string} key The removed row key
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event removeColumn
|
||||
*
|
||||
* Fired when a column is removed from the table.
|
||||
*
|
||||
* @param {number} index The removed column index
|
||||
* @param {string} key The removed column key
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event clear
|
||||
*
|
||||
* Fired when the table data is wiped.
|
||||
*
|
||||
* @param {boolean} clear Clear row/column properties
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Initializes and ensures the proper creation of the rows and cols property arrays.
|
||||
* If data exceeds the number of rows and cols given, new ones will be created.
|
||||
*
|
||||
* @private
|
||||
* @param {Array} rowProps The initial row props
|
||||
* @param {Array} colProps The initial column props
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.initializeProps = function ( rowProps, colProps ) {
|
||||
// FIXME: Account for extra data with missing row/col metadata
|
||||
|
||||
var i, len;
|
||||
|
||||
this.rows = [];
|
||||
this.cols = [];
|
||||
|
||||
if ( Array.isArray( rowProps ) ) {
|
||||
for ( i = 0, len = rowProps.length; i < len; i++ ) {
|
||||
this.rows.push( {
|
||||
index: i,
|
||||
key: rowProps[ i ].key,
|
||||
label: rowProps[ i ].label
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
if ( Array.isArray( colProps ) ) {
|
||||
for ( i = 0, len = colProps.length; i < len; i++ ) {
|
||||
this.cols.push( {
|
||||
index: i,
|
||||
key: colProps[ i ].key,
|
||||
label: colProps[ i ].label
|
||||
} );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers the initialization process and builds the initial table.
|
||||
*
|
||||
* @fires insertRow
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.setupTable = function () {
|
||||
this.verifyData();
|
||||
this.buildTable();
|
||||
};
|
||||
|
||||
/**
|
||||
* Verifies if the table data is complete and synced with
|
||||
* row and column properties, and adds empty strings as
|
||||
* cell data if cells are missing
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.verifyData = function () {
|
||||
var i, j, rowLen, colLen;
|
||||
|
||||
for ( i = 0, rowLen = this.rows.length; i < rowLen; i++ ) {
|
||||
if ( this.data[ i ] === undefined ) {
|
||||
this.data.push( [] );
|
||||
}
|
||||
|
||||
for ( j = 0, colLen = this.cols.length; j < colLen; j++ ) {
|
||||
if ( this.data[ i ][ j ] === undefined ) {
|
||||
this.data[ i ].push( '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Build initial table
|
||||
*
|
||||
* @private
|
||||
* @fires insertRow
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.buildTable = function () {
|
||||
var i, len;
|
||||
|
||||
for ( i = 0, len = this.rows.length; i < len; i++ ) {
|
||||
this.emit( 'insertRow', this.data[ i ], i, this.rows[ i ].key, this.rows[ i ].label );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the entire table with new data
|
||||
*
|
||||
* @private
|
||||
* @fires insertRow
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.refreshTable = function () {
|
||||
// TODO: Clear existing table
|
||||
|
||||
this.buildTable();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of a particular cell
|
||||
*
|
||||
* @param {number|string} row The index or key of the row
|
||||
* @param {number|string} col The index or key of the column
|
||||
* @param {Mixed} value The new value
|
||||
* @fires valueChange
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.setValue = function ( row, col, value ) {
|
||||
var rowIndex, colIndex;
|
||||
|
||||
if ( typeof row === 'number' ) {
|
||||
rowIndex = row;
|
||||
} else if ( typeof row === 'string' ) {
|
||||
rowIndex = this.getRowProperties( row ).index;
|
||||
}
|
||||
|
||||
if ( typeof col === 'number' ) {
|
||||
colIndex = col;
|
||||
} else if ( typeof col === 'string' ) {
|
||||
colIndex = this.getColumnProperties( col ).index;
|
||||
}
|
||||
|
||||
if ( typeof rowIndex === 'number' && typeof colIndex === 'number' &&
|
||||
this.data[ rowIndex ] !== undefined && this.data[ rowIndex ][ colIndex ] !== undefined &&
|
||||
this.data[ rowIndex ][ colIndex ] !== value ) {
|
||||
|
||||
this.data[ rowIndex ][ colIndex ] = value;
|
||||
this.emit( 'valueChange', rowIndex, colIndex, value );
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the table data
|
||||
*
|
||||
* @param {Array} data The new table data
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.setData = function ( data ) {
|
||||
if ( Array.isArray( data ) ) {
|
||||
this.data = data;
|
||||
|
||||
this.verifyData();
|
||||
this.refreshTable();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts a row into the table. If the row isn't added at the end of the table,
|
||||
* all the following data will be shifted back one row.
|
||||
*
|
||||
* @param {Array} [data] The data to insert to the row.
|
||||
* @param {number} [index] The index in which to insert the new row.
|
||||
* If unset or set to null, the row will be added at the end of the table.
|
||||
* @param {string} [key] A key to quickly select this row.
|
||||
* If unset or set to null, no key will be set.
|
||||
* @param {string} [label] A label to display next to the row.
|
||||
* If unset or set to null, the key will be used if there is one.
|
||||
* @fires insertRow
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.insertRow = function ( data, index, key, label ) {
|
||||
var insertIndex = ( typeof index === 'number' ) ? index : this.rows.length,
|
||||
newRowData = [],
|
||||
insertData, insertDataCell, i, len;
|
||||
|
||||
// Add the new row metadata
|
||||
this.rows.splice( insertIndex, 0, {
|
||||
index: insertIndex,
|
||||
key: key || undefined,
|
||||
label: label || undefined
|
||||
} );
|
||||
|
||||
// Add the new row data
|
||||
insertData = ( Array.isArray( data ) ) ? data : [];
|
||||
// Ensure that all columns of data for this row have been supplied,
|
||||
// otherwise fill the remaining data with empty strings
|
||||
for ( i = 0, len = this.cols.length; i < len; i++ ) {
|
||||
insertDataCell = '';
|
||||
if ( typeof insertData[ i ] === 'string' || typeof insertData[ i ] === 'number' ) {
|
||||
insertDataCell = insertData[ i ];
|
||||
}
|
||||
|
||||
newRowData.push( insertDataCell );
|
||||
}
|
||||
this.data.splice( insertIndex, 0, newRowData );
|
||||
|
||||
// Update all indexes in following rows
|
||||
for ( i = insertIndex + 1, len = this.rows.length; i < len; i++ ) {
|
||||
this.rows[ i ].index++;
|
||||
}
|
||||
|
||||
this.emit( 'insertRow', data, insertIndex, key, label );
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts a column into the table. If the column isn't added at the end of the table,
|
||||
* all the following data will be shifted back one column.
|
||||
*
|
||||
* @param {Array} [data] The data to insert to the column.
|
||||
* @param {number} [index] The index in which to insert the new column.
|
||||
* If unset or set to null, the column will be added at the end of the table.
|
||||
* @param {string} [key] A key to quickly select this column.
|
||||
* If unset or set to null, no key will be set.
|
||||
* @param {string} [label] A label to display next to the column.
|
||||
* If unset or set to null, the key will be used if there is one.
|
||||
* @fires insertColumn
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.insertColumn = function ( data, index, key, label ) {
|
||||
var insertIndex = ( typeof index === 'number' ) ? index : this.cols.length,
|
||||
insertDataCell, insertData, i, len;
|
||||
|
||||
// Add the new column metadata
|
||||
this.cols.splice( insertIndex, 0, {
|
||||
index: insertIndex,
|
||||
key: key || undefined,
|
||||
label: label || undefined
|
||||
} );
|
||||
|
||||
// Add the new column data
|
||||
insertData = ( Array.isArray( data ) ) ? data : [];
|
||||
// Ensure that all rows of data for this column have been supplied,
|
||||
// otherwise fill the remaining data with empty strings
|
||||
for ( i = 0, len = this.rows.length; i < len; i++ ) {
|
||||
insertDataCell = '';
|
||||
if ( typeof insertData[ i ] === 'string' || typeof insertData[ i ] === 'number' ) {
|
||||
insertDataCell = insertData[ i ];
|
||||
}
|
||||
|
||||
this.data[ i ].splice( insertIndex, 0, insertDataCell );
|
||||
}
|
||||
|
||||
// Update all indexes in following cols
|
||||
for ( i = insertIndex + 1, len = this.cols.length; i < len; i++ ) {
|
||||
this.cols[ i ].index++;
|
||||
}
|
||||
|
||||
this.emit( 'insertColumn', data, index, key, label );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a row from the table. If the row removed isn't at the end of the table,
|
||||
* all the following rows will be shifted back one row.
|
||||
*
|
||||
* @param {number|string} handle The key or numerical index of the row to remove
|
||||
* @fires removeRow
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.removeRow = function ( handle ) {
|
||||
var rowProps = this.getRowProperties( handle ),
|
||||
i, len;
|
||||
|
||||
// Exit early if the row couldn't be found
|
||||
if ( rowProps === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rows.splice( rowProps.index, 1 );
|
||||
this.data.splice( rowProps.index, 1 );
|
||||
|
||||
// Update all indexes in following rows
|
||||
for ( i = rowProps.index, len = this.rows.length; i < len; i++ ) {
|
||||
this.rows[ i ].index--;
|
||||
}
|
||||
|
||||
this.emit( 'removeRow', rowProps.index, rowProps.key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a column from the table. If the column removed isn't at the end of the table,
|
||||
* all the following columns will be shifted back one column.
|
||||
*
|
||||
* @param {number|string} handle The key or numerical index of the column to remove
|
||||
* @fires removeColumn
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.removeColumn = function ( handle ) {
|
||||
var colProps = this.getColumnProperties( handle ),
|
||||
i, len;
|
||||
|
||||
// Exit early if the column couldn't be found
|
||||
if ( colProps === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cols.splice( colProps.index, 1 );
|
||||
|
||||
for ( i = 0, len = this.data.length; i < len; i++ ) {
|
||||
this.data[ i ].splice( colProps.index, 1 );
|
||||
}
|
||||
|
||||
// Update all indexes in following columns
|
||||
for ( i = colProps.index, len = this.cols.length; i < len; i++ ) {
|
||||
this.cols[ i ].index--;
|
||||
}
|
||||
|
||||
this.emit( 'removeColumn', colProps.index, colProps.key );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the table data
|
||||
*
|
||||
* @fires clear
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.clear = function () {
|
||||
this.data = [];
|
||||
this.verifyData();
|
||||
|
||||
this.emit( 'clear', false );
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the table data, as well as all row and column properties
|
||||
*
|
||||
* @fires clear
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.clearWithProperties = function () {
|
||||
this.data = [];
|
||||
this.rows = [];
|
||||
this.cols = [];
|
||||
|
||||
this.emit( 'clear', true );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all table properties
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getTableProperties = function () {
|
||||
return {
|
||||
showHeaders: this.showHeaders,
|
||||
showRowLabels: this.showRowLabels,
|
||||
allowRowInsertion: this.allowRowInsertion,
|
||||
allowRowDeletion: this.allowRowDeletion
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the validation pattern to test cells against
|
||||
*
|
||||
* @return {RegExp|Function|string}
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getValidationPattern = function () {
|
||||
return this.validate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of a given row
|
||||
*
|
||||
* @param {string|number} handle The key (or numeric index) of the row
|
||||
* @return {Object|null} An object containing the `key`, `index` and `label` properties of the row.
|
||||
* Returns `null` if the row can't be found.
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getRowProperties = function ( handle ) {
|
||||
return mw.widgets.TableWidgetModel.static.getEntryFromPropsTable( handle, this.rows );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of all rows
|
||||
*
|
||||
* @return {Array} An array of objects containing `key`, `index` and `label` properties for each row
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getAllRowProperties = function () {
|
||||
return this.rows.slice();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of a given column
|
||||
*
|
||||
* @param {string|number} handle The key (or numeric index) of the column
|
||||
* @return {Object|null} An object containing the `key`, `index` and
|
||||
* `label` properties of the column.
|
||||
* Returns `null` if the column can't be found.
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getColumnProperties = function ( handle ) {
|
||||
return mw.widgets.TableWidgetModel.static.getEntryFromPropsTable( handle, this.cols );
|
||||
};
|
||||
|
||||
/**
|
||||
* Get properties of all columns
|
||||
*
|
||||
* @return {Array} An array of objects containing `key`, `index` and
|
||||
* `label` properties for each column
|
||||
*/
|
||||
mw.widgets.TableWidgetModel.prototype.getAllColumnProperties = function () {
|
||||
return this.cols.slice();
|
||||
};
|
||||
|
|
@ -99,6 +99,7 @@ return [
|
|||
'tests/qunit/suites/resources/mediawiki.rcfilters/dm.SavedQueriesModel.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.rcfilters/UriProcessor.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.widgets/MediaSearch/mediawiki.widgets.APIResultsQueue.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.widgets/Table/mediawiki.widgets.TableWidget.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
/*!
|
||||
* MediaWiki Widgets TableWidget tests.
|
||||
*/
|
||||
|
||||
QUnit.module( 'mediawiki.widgets.TableWidget' );
|
||||
|
||||
( function () {
|
||||
// The test verifes columns can be inserted and seem valid. However the
|
||||
// code itself is bugged and columns end up duplicated.
|
||||
//
|
||||
// See https://phabricator.wikimedia.org/T151262#4253730
|
||||
QUnit.skip( 'mw.widgets.TableWidget', function ( assert ) {
|
||||
var widgetA = new mw.widgets.TableWidget( {
|
||||
rows: [
|
||||
{
|
||||
key: 'foo',
|
||||
label: 'Foo'
|
||||
},
|
||||
{
|
||||
key: 'bar',
|
||||
label: 'Bar'
|
||||
}
|
||||
],
|
||||
cols: [
|
||||
{
|
||||
label: 'A'
|
||||
},
|
||||
{
|
||||
label: 'B'
|
||||
}
|
||||
]
|
||||
} ),
|
||||
widgetB = new mw.widgets.TableWidget( {
|
||||
rows: [
|
||||
{
|
||||
key: 'foo'
|
||||
},
|
||||
{
|
||||
key: 'bar'
|
||||
}
|
||||
],
|
||||
cols: [
|
||||
{}, {}, {}
|
||||
],
|
||||
data: [
|
||||
[ '11', '12', '13' ],
|
||||
[ '21', '22', '23' ]
|
||||
]
|
||||
} ),
|
||||
widgetAexpectedRowProps = {
|
||||
index: 1,
|
||||
key: 'bar',
|
||||
label: 'Bar'
|
||||
},
|
||||
widgetAexpectedInitialData = [
|
||||
[ '', '' ],
|
||||
[ '', '' ]
|
||||
],
|
||||
widgetAexpectedDataAfterValue = [
|
||||
[ '', '' ],
|
||||
[ '3', '' ]
|
||||
],
|
||||
widgetAexpectedDataAfterRowColumnInsertions = [
|
||||
[ '', '', '' ],
|
||||
[ 'a', 'b', 'c' ],
|
||||
[ '3', '', '' ]
|
||||
],
|
||||
widgetAexpectedDataAfterColumnRemoval = [
|
||||
[ '', '' ],
|
||||
[ 'b', 'c' ],
|
||||
[ '', '' ]
|
||||
];
|
||||
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedInitialData, 'Table data is initialized properly' );
|
||||
|
||||
assert.deepEqual( widgetA.model.getRowProperties( 'bar' ), widgetAexpectedRowProps, 'Row props are returned successfully' );
|
||||
|
||||
widgetA.setValue( 'bar', 0, '3' );
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedDataAfterValue, 'Table data is modified successfully' );
|
||||
|
||||
widgetA.insertColumn();
|
||||
widgetA.insertRow( [ 'a', 'b', 'c' ], 1, 'baz', 'Baz' );
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedDataAfterRowColumnInsertions, 'Row and column are added successfully' );
|
||||
|
||||
widgetA.removeColumn( 0 );
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedDataAfterColumnRemoval, 'Columns are removed successfully' );
|
||||
|
||||
widgetA.removeRow( -1 );
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedDataAfterColumnRemoval, 'Invalid row removal by index does not change table data' );
|
||||
|
||||
widgetA.removeRow( 'qux' );
|
||||
assert.deepEqual( widgetA.model.data, widgetAexpectedDataAfterColumnRemoval, 'Invalid row removal by key does not change table data' );
|
||||
|
||||
assert.deepEqual( widgetB.getItems()[ 0 ].getItems()[ 2 ].getValue(), '13', 'Initial data is populated in text inputs properly' );
|
||||
} );
|
||||
}() );
|
||||
Loading…
Reference in a new issue