all files / src/src/widgets/ CheckboxMultiselectWidget.js

20.41% Statements 10/49
0% Branches 0/29
16.67% Functions 1/6
21.28% Lines 10/47
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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159                                                                    624×     624×     624×     624×                                                                                                                                                                                                                          
/**
 * CheckboxMultiselectWidget is a {@link OO.ui.MultiselectWidget multiselect widget} that contains
 * checkboxes and is used together with OO.ui.CheckboxMultioptionWidget. The
 * CheckboxMultiselectWidget provides an interface for adding, removing and selecting options.
 * Please see the [OOUI documentation on MediaWiki][1] for more information.
 *
 * If you want to use this within an HTML form, such as a OO.ui.FormLayout, use
 * OO.ui.CheckboxMultiselectInputWidget instead.
 *
 * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options
 *
 *     @example
 *     // A CheckboxMultiselectWidget with CheckboxMultioptions.
 *     const option1 = new OO.ui.CheckboxMultioptionWidget( {
 *             data: 'a',
 *             selected: true,
 *             label: 'Selected checkbox'
 *         } ),
 *         option2 = new OO.ui.CheckboxMultioptionWidget( {
 *             data: 'b',
 *             label: 'Unselected checkbox'
 *         } ),
 *         multiselect = new OO.ui.CheckboxMultiselectWidget( {
 *             items: [ option1, option2 ]
 *         } );
 *     $( document.body ).append( multiselect.$element );
 *
 * @class
 * @extends OO.ui.MultiselectWidget
 *
 * @constructor
 * @param {Object} [config] Configuration options
 */
OO.ui.CheckboxMultiselectWidget = function OoUiCheckboxMultiselectWidget( config ) {
	// Parent constructor
	OO.ui.CheckboxMultiselectWidget.super.call( this, config );
 
	// Properties
	this.$lastClicked = null;
 
	// Events
	this.$group.on( 'click', this.onClick.bind( this ) );
 
	// Initialization
	this.$element.addClass( 'oo-ui-checkboxMultiselectWidget' );
};
 
/* Setup */
 
OO.inheritClass( OO.ui.CheckboxMultiselectWidget, OO.ui.MultiselectWidget );
 
/* Methods */
 
/**
 * Get an option by its position relative to the specified item (or to the start of the
 * option array, if item is `null`). The direction in which to search through the option array
 * is specified with a number: -1 for reverse (the default) or 1 for forward. The method will
 * return an option, or `null` if there are no options in the array.
 *
 * @param {OO.ui.CheckboxMultioptionWidget|null} item Item to describe the start position, or
 *  `null` to start at the beginning of the array.
 * @param {number} direction Direction to move in: -1 to move backward, 1 to move forward
 * @return {OO.ui.CheckboxMultioptionWidget|null} Item at position, `null` if there are no items
 *  in the select.
 */
OO.ui.CheckboxMultiselectWidget.prototype.getRelativeFocusableItem = function ( item, direction ) {
	const increase = direction > 0 ? 1 : -1,
		len = this.items.length;
 
	let nextIndex;
	if ( item ) {
		const currentIndex = this.items.indexOf( item );
		nextIndex = ( currentIndex + increase + len ) % len;
	} else {
		// If no item is selected and moving forward, start at the beginning.
		// If moving backward, start at the end.
		nextIndex = direction > 0 ? 0 : len - 1;
	}
 
	for ( let i = 0; i < len; i++ ) {
		item = this.items[ nextIndex ];
		if ( item && !item.isDisabled() ) {
			return item;
		}
		nextIndex = ( nextIndex + increase + len ) % len;
	}
	return null;
};
 
/**
 * Handle click events on checkboxes.
 *
 * @param {jQuery.Event} e
 */
OO.ui.CheckboxMultiselectWidget.prototype.onClick = function ( e ) {
	const $lastClicked = this.$lastClicked,
		$nowClicked = $( e.target ).closest( '.oo-ui-checkboxMultioptionWidget' )
			.not( '.oo-ui-widget-disabled' );
 
	// Allow selecting multiple options at once by Shift-clicking them
	if ( $lastClicked && $nowClicked.length && e.shiftKey ) {
		const $options = this.$group.find( '.oo-ui-checkboxMultioptionWidget' );
		const lastClickedIndex = $options.index( $lastClicked );
		const nowClickedIndex = $options.index( $nowClicked );
		// If it's the same item, either the user is being silly, or it's a fake event generated
		// by the browser. In either case we don't need custom handling.
		if ( nowClickedIndex !== lastClickedIndex ) {
			const items = this.items;
			const wasSelected = items[ nowClickedIndex ].isSelected();
			const direction = nowClickedIndex > lastClickedIndex ? 1 : -1;
 
			// This depends on the DOM order of the items and the order of the .items array being
			// the same.
			for ( let i = lastClickedIndex; i !== nowClickedIndex; i += direction ) {
				if ( !items[ i ].isDisabled() ) {
					items[ i ].setSelected( !wasSelected );
				}
			}
			// For the now-clicked element, use immediate timeout to allow the browser to do its own
			// handling first, then set our value. The order in which events happen is different for
			// clicks on the <input> and on the <label> and there are additional fake clicks fired
			// for non-click actions that change the checkboxes.
			e.preventDefault();
			setTimeout( function () {
				if ( !items[ nowClickedIndex ].isDisabled() ) {
					items[ nowClickedIndex ].setSelected( !wasSelected );
				}
			} );
		}
	}
 
	if ( $nowClicked.length ) {
		this.$lastClicked = $nowClicked;
	}
};
 
/**
 * Focus the widget
 *
 * @chainable
 * @return {OO.ui.Widget} The widget, for chaining
 */
OO.ui.CheckboxMultiselectWidget.prototype.focus = function () {
	if ( !this.isDisabled() ) {
		const item = this.getRelativeFocusableItem( null, 1 );
		if ( item ) {
			item.focus();
		}
	}
	return this;
};
 
/**
 * @inheritdoc
 */
OO.ui.CheckboxMultiselectWidget.prototype.simulateLabelClick = function () {
	this.focus();
};