import * as React from 'react'; import { MmuiTooltipComponent } from '../MmuiTooltipComponent'; import { parseChoice } from './utils'; export interface Props { id?: any; title?: string; summaryRenderMode?: string; summaryTextDisplaySep?: string; hasFilterInput?: boolean; //whether there is a filter input field to visually filter option choices hasSelectAll?: boolean; /// whether there is a select all checkbox to toggle between selecting or un-selecting all choices. filterPlaceholder?: string; data?: any; //data used to construct the list of choices } export interface State { id: string; // unique id for the Component title: string; //string that is displayed if there are no option choices selected. summaryRenderMode: string; //"text" or "token", determines if the summary is displayed as text or as tokens summaryTextDisplaySep: string; //if summary render mode is "text", filterPlaceholder: string; //placeholder text for the filter input box filterValue: string; //default value for the filter input field choices: any[]; //the options in the multi-select, each can have a children list selectedChoiceCount: number; // number of parents that have been selected isOpen: boolean; //tracks whether the Component is in the expanded state or not. tooltipX: number; tooltipY: number; tooltipBody: string; hasTooltip: boolean; } export abstract class MmuiDropChoiceComponent< P extends Props > extends React.Component
{
protected id;
protected static id_counter = 0;
protected readonly expandedCharacterCount = 32;
protected readonly closedCharacterCount = 13;
/**
* Generate a unique id for this component instance
*/
protected getId(): number {
if (this.id === undefined) {
this.id = MmuiDropChoiceComponent.id_counter;
MmuiDropChoiceComponent.id_counter++;
}
return this.id;
}
/**
* Constructor
* @param props
* props.summaryRenderMode: Options "text" or "token".
* Specifying "text" the summary of selected choices is display as text.
* Specifying "token" the summary of selected choices is display as tokens.
*/
constructor(props: P) {
super(props);
const state = {
id: 'mmui_drop_choice_component_' + this.getId(),
title: 'Select an option',
summaryRenderMode: 'text',
summaryTextDisplaySep: ', ',
filterPlaceholder: 'Filter options',
filterValue: '',
choices: [],
selectedChoiceCount: 0,
isOpen: false,
tooltipX: 0,
tooltipY: 0,
tooltipBody: '',
hasTooltip: false,
};
if (props.id) {
state.id = props.id;
}
if (props.title) {
state.title = props.title;
}
if (props.summaryRenderMode) {
state.summaryRenderMode = props.summaryRenderMode;
}
if (props.summaryTextDisplaySep) {
state.summaryTextDisplaySep = props.summaryTextDisplaySep;
}
if (props.filterPlaceholder) {
state.filterPlaceholder = props.filterPlaceholder;
}
if (props.data) {
state.choices = props.data.choices;
for (const choice of state.choices) {
if (choice.isChecked) {
state.selectedChoiceCount = state.selectedChoiceCount + 1;
}
}
}
this.state = state;
}
/**
* Toggles the isOpen state
* @param e
*/
headerClick(e, config?: any) {
e.preventDefault();
document.addEventListener('click', this.onBlur);
const setStateCallback =
config && config.setStateCallback
? config.setStateCallback
: undefined;
this.setState({ isOpen: !this.state.isOpen }, setStateCallback);
}
/**
* Listens for clicks on the Drop Choice header.
* @param e
*/
onHeaderClick = (e: React.SyntheticEvent) => {
this.headerClick(e);
};
/**
* Detects user clicks outside of the visual area of the Component.
* @param e
*/
onBlur = (e) => {
this.blur(e);
};
/**
* Sets state to collapsed (isOpen == false) and cleans up the event listeners.
* @param e
*/
blur(e, config?: any) {
if (this.state.isOpen) {
let elmt = event.target as HTMLElement,
isFieldClick = false;
while (elmt) {
isFieldClick = isFieldClick || elmt.id === this.state.id;
if (isFieldClick) {
break;
}
elmt = elmt.parentElement;
}
if (!isFieldClick) {
const setStateCallback =
config && config.setStateCallback
? config.setStateCallback
: undefined;
this.setState({ isOpen: false }, setStateCallback);
document.removeEventListener('click', this.onBlur);
}
}
}
onTokenMouseOut = () => {
this.setState({
tooltipBody: '',
tooltipX: 0,
tooltipY: 0,
hasTooltip: false,
});
};
onTokenMouseOver = (e: React.SyntheticEvent) => {
const tokenElmt = e.target as HTMLElement;
if (tokenElmt.classList.contains('mmui-token-remove')) {
return;
}
const componentElmt = document.getElementById(this.state.id);
const tokenRect = tokenElmt.getBoundingClientRect();
const componentRect = componentElmt.getBoundingClientRect();
const x = tokenRect.x - componentRect.x;
// also, subtract half the height of the tooltip + arrow
const y = tokenRect.y - componentRect.y - 70;
const tooltipBody = tokenElmt.dataset.tokenDisplay;
if(tooltipBody){
this.setState({
tooltipBody: tooltipBody,
tooltipX: x,
tooltipY: y,
hasTooltip: true,
});
}
};
abstract removeToken(choiceId);
/**
* Remove the target token from state
* @param e: onClick event
*/
onTokenRemove = (e: React.SyntheticEvent) => {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
const choiceTokenElmt = e.target as HTMLElement,
choiceId = choiceTokenElmt.dataset.tokenId;
this.removeToken(choiceId);
};
abstract updateChoice(newChoice);
/**
* Listens for choice state changes
* @param e
*/
onChoiceChange = (e: React.SyntheticEvent) => {
const newChoice = parseChoice(e.target as HTMLInputElement);
this.updateChoice(newChoice);
};
abstract applyFilter(filterValue: string);
/**
* Listens for filter value changes and hides any choices with a display that doesn't contain the filter value.
* @param e
*/
onFilterChange = (e: React.SyntheticEvent) => {
const filterValue = (e.target as HTMLInputElement).value;
this.applyFilter(filterValue);
this.setState({ filterValue: filterValue });
};
getSelectAllCheckRender() {
return;
}
abstract getTextSummaryRender();
abstract getTokenSummaryRender();
abstract renderChoice(choice);
/**
* Render the Component
*/
render() {
const choiceInputElmts = this.state.choices.map((choice) => {
return this.renderChoice(choice);
}
);
let summaryDisplayRender;
if (this.state.summaryRenderMode === 'token') {
summaryDisplayRender = this.getTokenSummaryRender();
} else {
summaryDisplayRender = this.getTextSummaryRender();
}
let filterInput;
if (this.props.hasFilterInput) {
filterInput = (
);
}
const selectAllCheck = this.getSelectAllCheckRender();
let tooltip;
const isOpenTooltipCheck =
this.state.isOpen &&
this.state.tooltipBody.length > this.expandedCharacterCount;
const isClosedTooltipCheck =
!this.state.isOpen &&
this.state.tooltipBody.length > this.closedCharacterCount;
if (
this.state.hasTooltip &&
(isOpenTooltipCheck || isClosedTooltipCheck)
) {
tooltip = (