# all files / src/widgets/ CheckboxMultiselectWidget.js

21.74% Statements 10/46
0% Branches 0/29
16.67% Functions 1/6
21.74% Lines 10/46

    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
    160
    161

    1×

    1592×

    1592×

    1592×

    1592×

    1×

    1×

    1×

    1×

    1×

     /**
     * 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 [OOjs UI 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.
     *
     *     @example
     *     // A CheckboxMultiselectWidget with CheckboxMultioptions.
     *     var option1 = new OO.ui.CheckboxMultioptionWidget( {
     *         data: 'a',
     *         selected: true,
     *         label: 'Selected checkbox'
     *     } );
     *
     *     var option2 = new OO.ui.CheckboxMultioptionWidget( {
     *         data: 'b',
     *         label: 'Unselected checkbox'
     *     } );
     *
     *     var multiselect=new OO.ui.CheckboxMultiselectWidget( {
     *         items: [ option1, option2 ]
     *      } );
     *
     *     $( 'body' ).append( multiselect.$element );
     *
     * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
     *
     * @class
     * @extends OO.ui.MultiselectWidget
     *
     * @constructor
     * @param {Object} [config] Configuration options
     */
    OO.ui.CheckboxMultiselectWidget = function OoUiCheckboxMultiselectWidget( config ) {
    	// Parent constructor
    	OO.ui.CheckboxMultiselectWidget.parent.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 ) {
    	var currentIndex, nextIndex, i,
    		increase = direction > 0 ? 1 : -1,
    		len = this.items.length;

    	if ( item ) {
    		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 ( 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 ) {
    	var $options, lastClickedIndex, nowClickedIndex, i, direction, wasSelected, items,
    		$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 ) {
    		$options = this.$group.find( '.oo-ui-checkboxMultioptionWidget' );
    		lastClickedIndex = $options.index( $lastClicked );
    		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 ) {
    			items = this.items;
    			wasSelected = items[ nowClickedIndex ].isSelected();
    			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 ( 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
     */
    OO.ui.CheckboxMultiselectWidget.prototype.focus = function () {
    	var item;
    	if ( !this.isDisabled() ) {
    		item = this.getRelativeFocusableItem( null, 1 );
    		if ( item ) {
    			item.focus();
    		}
    	}
    	return this;
    };

    /**
     * @inheritdoc
     */
    OO.ui.CheckboxMultiselectWidget.prototype.simulateLabelClick = function () {
    	this.focus();
    };