wikimedia/mediawiki-core

View on GitHub
resources/src/mediawiki.widgets/Table/mw.widgets.RowWidgetModel.js

Summary

Maintainability
D
1 day
Test Coverage
/*!
 * MediaWiki Widgets RowWidgetModel class
 *
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * RowWidget model.
 *
 * @class
 * @mixes OO.EventEmitter
 *
 * @constructor
 * @param {Object} [config] Configuration options
 * @param {Array} [config.data] An array containing all values of the row
 * @param {Array} [config.keys] An array of keys for easy cell selection
 * @param {RegExp|Function|string} [config.validate] Validation pattern to apply on every cell
 * @param {string} [config.label=''] Row label. Defaults to empty string.
 * @param {boolean} [config.showLabel=true] Show row label. Defaults to true.
 * @param {boolean} [config.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 */

/**
 * Fired when a value inside the row has changed.
 *
 * @event mw.widgets.RowWidgetModel.valueChange
 * @param {number} index The column index of the updated cell
 * @param {number} value The new value
 */

/**
 * Fired when a new cell is inserted into the row.
 *
 * @event mw.widgets.RowWidgetModel.insertCell
 * @param {Array} data The initial data
 * @param {number} index The index in which to insert the new cell
 */

/**
 * Fired when a cell is removed from the row.
 *
 * @event mw.widgets.RowWidgetModel.removeCell
 * @param {number} index The removed cell index
 */

/**
 * Fired when the row is cleared.
 *
 * @event mw.widgets.RowWidgetModel.clear
 * @param {boolean} clear Clear cell properties
 */

/**
 * Fired when the row label might need to be updated.
 *
 * @event mw.widgets.RowWidgetModel.labelUpdate
 */

/* 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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 {any} value The new value
 * @fires mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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 mw.widgets.RowWidgetModel.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();
};