Files
edx-platform/lms/static/js/demographics_collection/MultiselectDropdown.jsx
Syed Ali Abbas Zaidi d7053a6783 fix: eslint autofixable issues (#32181)
* fix: eslint operator-linebreak issue

* fix: eslint quotes issue

* fix: react jsx indent and props issues

* fix: eslint trailing spaces issues

* fix: eslint line around directives issue

* fix: eslint semi rule

* fix: eslint newline per chain rule

* fix: eslint space infix ops rule

* fix: eslint space-in-parens issue

* fix: eslint space before function paren issue

* fix: eslint space before blocks issue

* fix: eslint arrow body style issue

* fix: eslint dot-location issue

* fix: eslint quotes issue

* fix: eslint quote props issue

* fix: eslint operator assignment issue

* fix: eslint new line after import issue

* fix: indent issues

* fix: operator assignment issue

* fix: all autofixable eslint issues

* fix: all react related fixable issues

* fix: autofixable eslint issues

* chore: remove all template literals

* fix: remaining autofixable issues

* fix: failing js test
2023-05-18 11:03:59 +05:00

166 lines
5.7 KiB
JavaScript

/* global gettext */
import React from 'react';
import PropTypes from 'prop-types';
class MultiselectDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
};
// this version of React does not support React.createRef()
this.buttonRef = null;
this.setButtonRef = (element) => {
this.buttonRef = element;
};
this.focusButton = this.focusButton.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
this.handleRemoveAllClick = this.handleRemoveAllClick.bind(this);
this.handleOptionClick = this.handleOptionClick.bind(this);
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeydown, false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeydown, false);
}
findOption(data) {
return this.props.options.find((o) => o.value == data || o.display_name == data);
}
focusButton() {
if (this.buttonRef) { this.buttonRef.focus(); }
}
handleKeydown(event) {
if (this.state.open && event.keyCode == 27) {
this.setState({open: false}, this.focusButton);
}
}
handleButtonClick(e) {
this.setState({open: !this.state.open});
}
handleRemoveAllClick(e) {
this.props.onChange([]);
this.focusButton();
e.stopPropagation();
}
handleOptionClick(e) {
const value = e.target.value;
const inSelected = this.props.selected.includes(value);
let newSelected = [...this.props.selected];
// if the option has its own onChange, trigger that instead
if (this.findOption(value).onChange) {
this.findOption(value).onChange(e.target.checked, value);
return;
}
// if checked, add value to selected list
if (e.target.checked && !inSelected) {
newSelected = newSelected.concat(value);
}
// if unchecked, remove value from selected list
if (!e.target.checked && inSelected) {
newSelected = newSelected.filter(i => i !== value);
}
this.props.onChange(newSelected);
}
renderSelected() {
if (this.props.selected.length == 0) {
return this.props.emptyLabel;
}
const selectedList = this.props.selected
.map(selected => this.findOption(selected).display_name)
.join(', ');
if (selectedList.length > 60) {
return selectedList.substring(0, 55) + '...';
}
return selectedList;
}
renderUnselect() {
return this.props.selected.length > 0 && (
<button id="unselect-button" disabled={this.props.disabled} aria-label="Clear all selected" onClick={this.handleRemoveAllClick}>{gettext('Clear all')}</button>
);
}
renderMenu() {
if (!this.state.open) {
return;
}
const options = this.props.options.map((option, index) => {
const checked = this.props.selected.includes(option.value);
return (
<div key={index} id={`${option.value}-option-container`} className="option-container">
<label className="option-label">
<input id={`${option.value}-option-checkbox`} className="option-checkbox" type="checkbox" value={option.value} checked={checked} onChange={this.handleOptionClick} />
<span className="pl-2">{option.display_name}</span>
</label>
</div>
);
});
return (
<fieldset id="multiselect-dropdown-fieldset" disabled={this.props.disabled}>
<legend className="sr-only">{this.props.label}</legend>
{options}
</fieldset>
);
}
render() {
return (
<div
className="multiselect-dropdown pb-3"
tabIndex={-1}
onBlur={e => {
// We need to make sure we only close and save the dropdown when
// the user blurs on the parent to an element other than it's children.
// essentially what this if statement is saying:
// if the newly focused target is NOT a child of the this element, THEN fire the onBlur function
// and close the dropdown.
if (!e.currentTarget.contains(e.relatedTarget)) {
this.props.onBlur(e);
this.setState({open: false});
}
}}
>
<label id="multiselect-dropdown-label" htmlFor="multiselect-dropdown">{this.props.label}</label>
<div className="form-control d-flex">
<button className="multiselect-dropdown-button" disabled={this.props.disabled} id="multiselect-dropdown-button" ref={this.setButtonRef} aria-haspopup="true" aria-expanded={this.state.open} aria-labelledby="multiselect-dropdown-label multiselect-dropdown-button" onClick={this.handleButtonClick}>
{this.renderSelected()}
</button>
{this.renderUnselect()}
</div>
<div>
{this.renderMenu()}
</div>
</div>
);
}
}
export {MultiselectDropdown};
MultiselectDropdown.propTypes = {
label: PropTypes.string,
emptyLabel: PropTypes.string,
options: PropTypes.array.isRequired,
selected: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
};