import * as React from 'react'; import { cloneChoice, getMmuiCheckedDropChoices } from './utils'; import { MmuiDropChoiceComponent, Props } from './MmuiDropChoiceComponent'; /** * Drop Choice Component * A multi-select dropdown that allows for choice filtering. * * token: a thing serving as a visible or tangible representation of a fact */ export interface MmuiDropChoiceMultipleProps extends Props { hasParentSelection: boolean; } export class MmuiDropChoiceMultipleComponent< P extends MmuiDropChoiceMultipleProps > extends MmuiDropChoiceComponent

{ static defaultProps = { hasParentSelection: true, }; /** * Constructor * @param props * */ constructor(props: P) { super(props); } getCheckedChoices() { return getMmuiCheckedDropChoices(this.state.choices); } /** * Update the state of the choice that matches the provided changed choice. * @param changedChoice */ updateChoice(changedChoice) { let choice, newChoice, childChoice, newChildChoice, newChoices = [], selectedChoiceCount = 0; for (let p = 0; p < this.state.choices.length; p++) { choice = this.state.choices[p]; newChoice = cloneChoice(choice); // if changed choice is a parent if (newChoice.id == changedChoice.id) { newChoice.isChecked = changedChoice.isChecked; newChoice.isIndeterminate = false; if (choice.children && choice.children.length > 0) { newChoice.children = []; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); //set all children to the same state as parent newChildChoice.isChecked = newChoice.isChecked; newChoice.children.push(newChildChoice); } } } else if (choice.children && choice.children.length > 0) { newChoice.children = []; let isAllChecked = true, isAnyChecked = false; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); if (newChildChoice.id === changedChoice.id) { newChildChoice.isChecked = changedChoice.isChecked; } isAllChecked = isAllChecked && newChildChoice.isChecked; isAnyChecked = isAnyChecked || newChildChoice.isChecked; newChoice.children.push(newChildChoice); } newChoice.isChecked = isAllChecked; if (isAllChecked) { newChoice.isIndeterminate = false; } else { newChoice.isIndeterminate = isAnyChecked; } } if (newChoice.isChecked) { selectedChoiceCount = selectedChoiceCount + 1; } newChoices.push(newChoice); } this.setState({ choices: newChoices, selectedChoiceCount: selectedChoiceCount, }); } applyFilter(filterValue: string) { let isVisibleParent, hasVisibleChildren, choice, newChoice, childChoice, newChildChoice, newChoices = []; for (let p = 0; p < this.state.choices.length; p++) { choice = this.state.choices[p]; newChoice = cloneChoice(choice); if (filterValue) { isVisibleParent = newChoice.display .toLowerCase() .includes(filterValue.toLowerCase()); hasVisibleChildren = false; if (choice.children && choice.children.length > 0) { newChoice.children = []; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); newChildChoice.isVisible = newChildChoice.display .toLowerCase() .includes(filterValue.toLowerCase()); hasVisibleChildren = hasVisibleChildren || newChildChoice.isVisible; newChoice.children.push(newChildChoice); } } newChoice.isVisible = isVisibleParent || hasVisibleChildren; } else { newChoice.isVisible = true; if (choice.children && choice.children.length > 0) { newChoice.children = []; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); newChildChoice.isVisible = true; newChoice.children.push(newChildChoice); } } } newChoices.push(newChoice); } this.setState({ choices: newChoices }); } /** * Called when the component's props or state has been updated. */ componentDidUpdate() { if (this.props.hasParentSelection) { let choice, choiceElmt; for (let p = 0; p < this.state.choices.length; p++) { choice = this.state.choices[p]; choiceElmt = document.getElementById(choice.id); choiceElmt.indeterminate = choice.isIndeterminate; } } } removeToken(choiceId: string, config?: any) { let choice, newChoice, childChoice, newChildChoice, newChoices = [], selectedChoiceCount = 0; for (let p = 0; p < this.state.choices.length; p++) { choice = this.state.choices[p]; newChoice = cloneChoice(choice); if (newChoice.id === choiceId) { newChoice.isChecked = false; if (choice.children && choice.children.length > 0) { newChoice.children = []; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); newChildChoice.isChecked = false; newChoice.children.push(newChildChoice); } } } else if (choice.children && choice.children.length > 0) { newChoice.children = []; let isAnyChecked = false; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); if (newChildChoice.id === choiceId) { newChildChoice.isChecked = false; newChoice.isChecked = false; } isAnyChecked = isAnyChecked || newChildChoice.isChecked; newChoice.children.push(newChildChoice); } if (!newChoice.isChecked) { newChoice.isIndeterminate = isAnyChecked; } } if (newChoice.isChecked) { selectedChoiceCount = selectedChoiceCount + 1; } newChoices.push(newChoice); } const setStateCallback = config && config.setStateCallback ? config.setStateCallback : undefined; this.setState( { choices: newChoices, selectedChoiceCount: selectedChoiceCount, }, setStateCallback ); } /** * Build the summary of selected choices using just text. */ getTextSummaryRender() { let summaryDisplayRender, summaryCountDisplay, //Converts the choice option object into a display value to be used in the summary. // eslint-disable-next-line @typescript-eslint/no-unused-vars choiceDisplayMapper = (choice, index = 0, parent = undefined) => { let display = choice.display, isCheckedParent = choice.isChecked && choice.children && choice.children.length > 0; if (parent) { display = `${parent.display} - ${display}`; } else if (isCheckedParent) { display = `${display} - All`; } return display; }; if (this.state.choices.length > 0) { let choice, choiceChild, choiceDisplays = []; for (let i = 0; i < this.state.choices.length; i++) { choice = this.state.choices[i]; if (choice.isChecked) { choiceDisplays.push(choiceDisplayMapper(choice, i)); } else if (choice.children) { for (let j = 0; j < choice.children.length; j++) { choiceChild = choice.children[j]; if (choiceChild.isChecked) { choiceDisplays.push( choiceDisplayMapper(choiceChild, j, choice) ); } } } } if (choiceDisplays.length > 0) { if (this.state.isOpen) { summaryDisplayRender = (

{choiceDisplays.join( this.state.summaryTextDisplaySep )}
); } else { if (choiceDisplays.length > 1) { const moreCountDisplay = `+${ choiceDisplays.length - 1 } more`; summaryCountDisplay = ( {moreCountDisplay} ); } summaryDisplayRender = (
{choiceDisplays[0]}{' '} {summaryCountDisplay}
); } } // display title, if no choices have been selected else { summaryDisplayRender = (
{this.state.title}
); } } // display title, if there are no choice options else { summaryDisplayRender = (
{this.state.title}
); } return summaryDisplayRender; } getTokenRender(choice, parent = undefined){ let key = `token-${choice.id}`, display = choice.display, isCheckedParent = choice.isChecked && choice.children && choice.children.length > 0; if (parent) { display = `${parent.display} - ${display}`; } else if (isCheckedParent) { display = `${display} - All`; } return (
{display}
) } /** * Build the summary of selected choices using tokens elements. */ getTokenSummaryRender() { let summaryDisplay, summaryCountDisplay, choiceTokenElmts = [], //Converts the choice option object into a token element to be used in the summary. // eslint-disable-next-line @typescript-eslint/no-unused-vars choiceTokenElmtMapper = (choice, index = 0, parent = undefined) => { return this.getTokenRender(choice, parent); }; if (this.state.choices.length > 0) { let choice, choiceChild; for (let i = 0; i < this.state.choices.length; i++) { choice = this.state.choices[i]; if (choice.isChecked) { choiceTokenElmts.push(choiceTokenElmtMapper(choice, i)); } else if (choice.children && choice.children.length > 0) { for (let j = 0; j < choice.children.length; j++) { choiceChild = choice.children[j]; if (choiceChild.isChecked) { choiceTokenElmts.push( choiceTokenElmtMapper(choiceChild, j, choice) ); } } } } if (choiceTokenElmts.length > 0) { if (this.state.isOpen) { summaryDisplay = (
{choiceTokenElmts}
); } else { summaryDisplay = (
{choiceTokenElmts[0]}
); } if (!this.state.isOpen && choiceTokenElmts.length > 1) { const moreCountDisplay = `+${ choiceTokenElmts.length - 1 } more`; summaryCountDisplay = (
{moreCountDisplay}
); } } // display title, if no choices have been selected else { summaryDisplay = (
{this.state.title}
); } } // display title, if there are no choice options else { summaryDisplay = (
{this.state.title}
); } return ( {summaryDisplay} {summaryCountDisplay} ); } /** * Select all or Unselect all based on target checked state. * @param e */ onChangeAllSelection = (e: React.SyntheticEvent) => { let choice, newChoice, childChoice, newChildChoice, newChoices = [], selectedChoiceCount = 0, isChecked = (e.target as HTMLInputElement).checked; for (let p = 0; p < this.state.choices.length; p++) { choice = this.state.choices[p]; newChoice = cloneChoice(choice); newChoice.isChecked = isChecked; newChoice.isIndeterminate = false; if (choice.children && choice.children.length > 0) { newChoice.children = []; for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; newChildChoice = cloneChoice(childChoice); newChildChoice.isChecked = isChecked; newChoice.children.push(newChildChoice); } } if (newChoice.isChecked) { selectedChoiceCount = selectedChoiceCount + 1; } newChoices.push(newChoice); } this.setState({ choices: newChoices, selectedChoiceCount: selectedChoiceCount, }); }; /** * Return the number of selected choice options. * @return */ static getSelectedChoicesCount(choices): number { let selectedCount = 0, choice, childChoice; for (let p = 0; p < choices.length; p++) { choice = choices[p]; if (choice.isChecked) { selectedCount++; } if (choice.children && choice.children.length > 0) { for (let c = 0; c < choice.children.length; c++) { childChoice = choice.children[c]; if (childChoice.isChecked) { selectedCount++; } } } } return selectedCount; } /** * Return the number of selected choice options from the current state. */ getSelectedCount(): number { return MmuiDropChoiceMultipleComponent.getSelectedChoicesCount( this.state.choices ); } getSelectAllCheckRender() { let selectAllCheck; if (this.props.hasSelectAll) { const selectAllCheckId = `select-all-${this.state.id}`, isAllSelected = this.state.selectedChoiceCount === this.state.choices.length; selectAllCheck = (
); } return selectAllCheck; } renderChoiceInput(choice) { const choiceInputProps: any = { id: choice.id, name: choice.name, value: choice.value, checked: choice.isChecked, }, choiceStyle: any = { display: 'block' }; if (choice.isParent) { choiceInputProps['data-isparent'] = 'true'; } else { choiceInputProps['data-parentid'] = choice.parentValue; } if (!choice.isVisible) { choiceStyle.display = 'none'; } if (!this.props.hasParentSelection && choice.children) { return {choice.display}; } return (
); } renderChoice(choice) { let key = `choice-input-${choice.id}`, choiceInputRender = this.renderChoiceInput(choice), childChoiceInputRenders; if (choice.children) { childChoiceInputRenders = choice.children.map((childChoice) => { return this.renderChoiceInput(childChoice); }); } return (
{choiceInputRender}
{childChoiceInputRenders}
); } }