1. Added support for Picker and Dropdown controls
This commit is contained in:
Родитель
195bb66766
Коммит
04eff223fa
70
README.md
70
README.md
|
@ -22,6 +22,8 @@ Some of the features of the Editable Grid are:-
|
|||
>- Flexibility to implement onChange callback on any cell value change (For cases like calculating summation of a column etc)
|
||||
>- Length Validations during edit
|
||||
>- Type Validations during edit
|
||||
>- Rule-Based Cell Styling
|
||||
>- In-built support for controls like TextField, Multiline-Textfield, Picker, Dropdown, Calendar
|
||||
>- The component is completely Accessible
|
||||
|
||||
## Clone & Run
|
||||
|
@ -141,6 +143,58 @@ This starts the project on port 3000 and you are ready to play around with the E
|
|||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.Date
|
||||
},
|
||||
{
|
||||
key: 'payrolltype',
|
||||
name: 'Payroll Type',
|
||||
text: 'Payroll Type',
|
||||
editable: true,
|
||||
dataType: 'string',
|
||||
minWidth: 150,
|
||||
maxWidth: 150,
|
||||
isResizable: true,
|
||||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.DropDown,
|
||||
dropdownValues: [
|
||||
{ key: 'weekly', text: 'Weekly' },
|
||||
{ key: 'biweekly', text: 'Bi-Weekly' },
|
||||
{ key: 'monthly', text: 'Monthly' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'employmenttype',
|
||||
name: 'Employment Type',
|
||||
text: 'Employment Type',
|
||||
editable: true,
|
||||
dataType: 'string',
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.Picker,
|
||||
pickerOptions: {
|
||||
pickerTags: ['Employment Type1', 'Employment Type2', 'Employment Type3', 'Employment Type4', 'Employment Type5', 'Employment Type6', 'Employment Type7', 'Employment Type8', 'Employment Type9', 'Employment Type10', 'Employment Type11', 'Employment Type12'],
|
||||
minCharLimitForSuggestions: 2,
|
||||
tagsLimit: 1,
|
||||
pickerDescriptionOptions: {
|
||||
enabled: true,
|
||||
values: [
|
||||
{ key: 'Employment Type1', description: 'Employment Type1 Description'},
|
||||
{ key: 'Employment Type2', description: 'Employment Type2 Description'},
|
||||
{ key: 'Employment Type3', description: 'Employment Type3 Description'},
|
||||
{ key: 'Employment Type4', description: 'Employment Type4 Description'},
|
||||
{ key: 'Employment Type5', description: 'Employment Type5 Description'},
|
||||
{ key: 'Employment Type6', description: 'Employment Type6 Description'},
|
||||
{ key: 'Employment Type7', description: 'Employment Type7 Description'},
|
||||
{ key: 'Employment Type8', description: 'Employment Type8 Description'},
|
||||
{ key: 'Employment Type9', description: 'Employment Type9 Description'},
|
||||
{ key: 'Employment Type10', description: 'Employment Type10 Description'},
|
||||
{ key: 'Employment Type11', description: 'Employment Type11 Description'},
|
||||
{ key: 'Employment Type12', description: 'Employment Type12 Description'},
|
||||
] }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -152,7 +206,9 @@ This starts the project on port 3000 and you are ready to play around with the E
|
|||
age:32,
|
||||
designation:'Designation1',
|
||||
salary:57000,
|
||||
dateofjoining:'2010-04-01T14:57:10'
|
||||
dateofjoining:'2010-04-01T14:57:10',
|
||||
payrolltype: 'Weekly',
|
||||
employmenttype: 'Employment Type11'
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
|
@ -160,7 +216,9 @@ This starts the project on port 3000 and you are ready to play around with the E
|
|||
age:27,
|
||||
designation:'Designation2',
|
||||
salary:42000,
|
||||
dateofjoining:'2014-06-09T14:57:10'
|
||||
dateofjoining:'2014-06-09T14:57:10',
|
||||
payrolltype: 'Monthly',
|
||||
employmenttype: 'Employment Type4'
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
|
@ -168,7 +226,9 @@ This starts the project on port 3000 and you are ready to play around with the E
|
|||
age:35,
|
||||
designation:'Designation3',
|
||||
salary:75000,
|
||||
dateofjoining:'2005-07-02T14:57:10'
|
||||
dateofjoining:'2005-07-02T14:57:10',
|
||||
payrolltype: 'Weekly',
|
||||
employmenttype: 'Employment Type7'
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
|
@ -176,7 +236,9 @@ This starts the project on port 3000 and you are ready to play around with the E
|
|||
age:30,
|
||||
designation:'Designation4',
|
||||
salary:49000,
|
||||
dateofjoining:'2019-04-01T14:57:10'
|
||||
dateofjoining:'2019-04-01T14:57:10',
|
||||
payrolltype: 'Bi-Weekly',
|
||||
employmenttype: 'Employment Type2'
|
||||
}
|
||||
];
|
||||
setItems(dummyData);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fluentui-editable-grid",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"license": "MIT",
|
||||
"description": "Wrapper over the existing DetailsList that makes in-place editability work like a dream(among many other new features)",
|
||||
"main": "dist/index.js",
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import { NumberAndDateOperators, StringOperators } from "../../libs/types/cellstyleruletype";
|
||||
import { IColumnConfig } from "../../libs/types/columnconfigtype";
|
||||
import { EditControlType } from "../../libs/types/editcontroltype";
|
||||
import { IGridItemsType } from "../../libs/types/griditemstype";
|
||||
|
||||
export const GridColumnConfig : IColumnConfig[] =
|
||||
[
|
||||
|
@ -96,6 +95,58 @@ export const GridColumnConfig : IColumnConfig[] =
|
|||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.Date
|
||||
},
|
||||
{
|
||||
key: 'payrolltype',
|
||||
name: 'Payroll Type',
|
||||
text: 'Payroll Type',
|
||||
editable: true,
|
||||
dataType: 'string',
|
||||
minWidth: 150,
|
||||
maxWidth: 150,
|
||||
isResizable: true,
|
||||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.DropDown,
|
||||
dropdownValues: [
|
||||
{ key: 'weekly', text: 'Weekly' },
|
||||
{ key: 'biweekly', text: 'Bi-Weekly' },
|
||||
{ key: 'monthly', text: 'Monthly' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'employmenttype',
|
||||
name: 'Employment Type',
|
||||
text: 'Employment Type',
|
||||
editable: true,
|
||||
dataType: 'string',
|
||||
minWidth: 200,
|
||||
maxWidth: 200,
|
||||
isResizable: true,
|
||||
includeColumnInExport: true,
|
||||
includeColumnInSearch: true,
|
||||
inputType: EditControlType.Picker,
|
||||
pickerOptions: {
|
||||
pickerTags: ['Employment Type1', 'Employment Type2', 'Employment Type3', 'Employment Type4', 'Employment Type5', 'Employment Type6', 'Employment Type7', 'Employment Type8', 'Employment Type9', 'Employment Type10', 'Employment Type11', 'Employment Type12'],
|
||||
minCharLimitForSuggestions: 2,
|
||||
tagsLimit: 1,
|
||||
pickerDescriptionOptions: {
|
||||
enabled: true,
|
||||
values: [
|
||||
{ key: 'Employment Type1', description: 'Employment Type1 Description'},
|
||||
{ key: 'Employment Type2', description: 'Employment Type2 Description'},
|
||||
{ key: 'Employment Type3', description: 'Employment Type3 Description'},
|
||||
{ key: 'Employment Type4', description: 'Employment Type4 Description'},
|
||||
{ key: 'Employment Type5', description: 'Employment Type5 Description'},
|
||||
{ key: 'Employment Type6', description: 'Employment Type6 Description'},
|
||||
{ key: 'Employment Type7', description: 'Employment Type7 Description'},
|
||||
{ key: 'Employment Type8', description: 'Employment Type8 Description'},
|
||||
{ key: 'Employment Type9', description: 'Employment Type9 Description'},
|
||||
{ key: 'Employment Type10', description: 'Employment Type10 Description'},
|
||||
{ key: 'Employment Type11', description: 'Employment Type11 Description'},
|
||||
{ key: 'Employment Type12', description: 'Employment Type12 Description'},
|
||||
] }
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -106,4 +157,6 @@ export interface GridItemsType {
|
|||
designation: string;
|
||||
salary: number;
|
||||
dateofjoining: string;
|
||||
payrolltype: string;
|
||||
employmenttype: string
|
||||
};
|
|
@ -37,13 +37,16 @@ const Consumer = () => {
|
|||
const SetDummyData = () : void => {
|
||||
var dummyData : GridItemsType[] = []
|
||||
for(var i = 1; i <= 100; i++){
|
||||
var randomInt = GetRandomInt(1,3);
|
||||
dummyData.push({
|
||||
id: i,
|
||||
name: 'Name'+ GetRandomInt(1, 10),
|
||||
age: GetRandomInt(20,40),
|
||||
designation: 'Designation' + GetRandomInt(1, 15),
|
||||
salary: GetRandomInt(35000, 75000),
|
||||
dateofjoining: '2010-10-10T14:57:10'
|
||||
dateofjoining: '2010-10-10T14:57:10',
|
||||
payrolltype: randomInt % 3 == 0 ? 'Weekly' : randomInt % 3 == 1 ? 'Bi-Weekly' : 'Monthly',
|
||||
employmenttype: 'Employment Type' + GetRandomInt(1,12)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -95,7 +98,7 @@ const Consumer = () => {
|
|||
enableGridRowsDelete={true}
|
||||
enableGridRowsAdd={true}
|
||||
height={'70vh'}
|
||||
width={'140vh'}
|
||||
width={'160vh'}
|
||||
position={'relative'}
|
||||
enableUnsavedEditIndicator={true}
|
||||
onGridSave={onGridSave}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { ConstrainMode, DatePicker, IStackStyles, IStackTokens, ITextFieldStyles, mergeStyleSets, Position, PrimaryButton, SpinButton, Stack, TextField } from "office-ui-fabric-react";
|
||||
import { ConstrainMode, DatePicker, Dropdown, IDropdownOption, IStackStyles, IStackTokens, ITag, ITextFieldStyles, mergeStyleSets, Position, PrimaryButton, SpinButton, Stack, TextField } from "office-ui-fabric-react";
|
||||
import React, { useState } from "react";
|
||||
import { IColumnConfig } from "../types/columnconfigtype";
|
||||
import { EditControlType } from "../types/editcontroltype";
|
||||
import { DayPickerStrings } from "./datepickerconfig";
|
||||
import { controlClass, horizontalGapStackTokens, stackStyles, textFieldStyles, verticalGapStackTokens } from "./editablegridstyles";
|
||||
import PickerControl from "./pickercontrol/picker";
|
||||
|
||||
interface Props {
|
||||
onChange: any;
|
||||
|
@ -16,6 +17,10 @@ const AddRowPanel = (props: Props) => {
|
|||
|
||||
const updateObj : any = {};
|
||||
|
||||
const onDropDownChange = (event: React.FormEvent<HTMLDivElement>, selectedDropdownItem: IDropdownOption | undefined, item : any): void => {
|
||||
updateObj[item.key] = selectedDropdownItem?.text;
|
||||
}
|
||||
|
||||
const onTextUpdate = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text: string): void => {
|
||||
updateObj[(ev.target as Element).id] = text;
|
||||
//console.log(updateObj);
|
||||
|
@ -25,6 +30,15 @@ const AddRowPanel = (props: Props) => {
|
|||
props.onChange(updateObj, props.enableRowsCounterField ? AddSpinRef.current.value : 1);
|
||||
};
|
||||
|
||||
const onCellPickerTagListChanged = (cellPickerTagList: ITag[] | undefined, item : any) : void => {
|
||||
if(cellPickerTagList && cellPickerTagList[0] && cellPickerTagList[0].name){
|
||||
updateObj[item.key] = cellPickerTagList[0].name;
|
||||
}
|
||||
else{
|
||||
updateObj[item.key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
const onCellDateChange = (date: Date | null | undefined, item : any): void => {
|
||||
updateObj[item.key] = date;
|
||||
};
|
||||
|
@ -44,6 +58,26 @@ const AddRowPanel = (props: Props) => {
|
|||
value={new Date()}
|
||||
/>);
|
||||
break;
|
||||
case EditControlType.DropDown:
|
||||
tmpRenderObj.push(
|
||||
<Dropdown
|
||||
label={item.text}
|
||||
options={item.dropdownValues ?? []}
|
||||
onChange={(ev, selected) => onDropDownChange(ev, selected, item)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case EditControlType.Picker:
|
||||
tmpRenderObj.push(<div>
|
||||
<span className={controlClass.pickerLabel}>{item.text}</span>
|
||||
<PickerControl
|
||||
selectedItemsLimit={1}
|
||||
pickerTags={item.pickerOptions?.pickerTags ?? []}
|
||||
minCharLimitForSuggestions={2}
|
||||
onTaglistChanged={(selectedItem: ITag[] | undefined) => onCellPickerTagListChanged(selectedItem, item)}
|
||||
pickerDescriptionOptions={item.pickerOptions?.pickerDescriptionOptions}
|
||||
/></div>);
|
||||
break;
|
||||
default:
|
||||
tmpRenderObj.push(<TextField
|
||||
name={item.text}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { TextField, ITextFieldStyles, ITextField } from 'office-ui-fabric-react/
|
|||
import { ContextualMenu, DirectionalHint, IContextualMenu, IContextualMenuProps } from 'office-ui-fabric-react/lib/ContextualMenu';
|
||||
import { useBoolean } from '@uifabric/react-hooks';
|
||||
import { IColumnConfig } from '../types/columnconfigtype';
|
||||
import { controlClass, GetDynamicSpanStyles, textFieldStyles } from './editablegridstyles';
|
||||
import { controlClass, dropdownStyles, GetDynamicSpanStyles, textFieldStyles } from './editablegridstyles';
|
||||
import { IGridItemsType } from '../types/griditemstype';
|
||||
import { Operation } from '../types/operation';
|
||||
import { InitializeInternalGrid, InitializeInternalGridEditStructure, ResetGridRowID, ShallowCopyDefaultGridToEditGrid, ShallowCopyEditGridToDefaultGrid } from './editablegridinitialize';
|
||||
|
@ -40,6 +40,8 @@ import FilterCallout from './columnfiltercallout/filtercallout';
|
|||
import { IRowAddWithValues } from '../types/rowaddtype';
|
||||
import AddRowPanel from './addrowpanel';
|
||||
import { Props } from '../types/editabledetailslistprops';
|
||||
import SearchableDropdown from './searchabledropdown/searchabledropdown';
|
||||
import PickerControl from './pickercontrol/picker';
|
||||
|
||||
const EditableGrid = (props: Props) => {
|
||||
const [editMode, setEditMode] = React.useState(false);
|
||||
|
@ -157,8 +159,8 @@ const EditableGrid = (props: Props) => {
|
|||
|
||||
useEffect(() => {
|
||||
UpdateGridEditStatus();
|
||||
// console.log('activate cell edit');
|
||||
// console.log(activateCellEdit);
|
||||
//console.log('activate cell edit');
|
||||
//console.log(activateCellEdit);
|
||||
}, [activateCellEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -560,6 +562,14 @@ const EditableGrid = (props: Props) => {
|
|||
EditCellValue(key, rowNum, activateCurrentCell);
|
||||
}
|
||||
|
||||
const onCellPickerDoubleClickEvent = (key : string, rowNum : number, activateCurrentCell : boolean) : void => {
|
||||
EditCellValue(key, rowNum, activateCurrentCell);
|
||||
}
|
||||
|
||||
const onDropdownDoubleClickEvent = (key : string, rowNum : number, activateCurrentCell : boolean) : void => {
|
||||
EditCellValue(key, rowNum, activateCurrentCell);
|
||||
}
|
||||
|
||||
const onKeyDownEvent = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>, column : IColumnConfig, rowNum : number, activateCurrentCell : boolean) : void => {
|
||||
if(event.key == "Enter"){
|
||||
if(!activateCellEdit[rowNum].isActivated){
|
||||
|
@ -584,6 +594,36 @@ const EditableGrid = (props: Props) => {
|
|||
setActivateCellEdit(activateCellEditTmp);
|
||||
};
|
||||
|
||||
const onCellPickerTagListChanged = (cellPickerTagList: ITag[] | undefined, row : number, column : IColumnConfig) : void => {
|
||||
setGridEditState(true);
|
||||
|
||||
let activateCellEditTmp : any[] = [];
|
||||
activateCellEdit.forEach((item, index) => {
|
||||
if(row == index){
|
||||
item.properties[column.key].value = (cellPickerTagList && cellPickerTagList[0] && cellPickerTagList[0].name) ? cellPickerTagList![0].name : '';
|
||||
}
|
||||
|
||||
activateCellEditTmp.push(item);
|
||||
});
|
||||
|
||||
setActivateCellEdit(activateCellEditTmp);
|
||||
}
|
||||
|
||||
const onDropDownChange = (event: React.FormEvent<HTMLDivElement>, selectedDropdownItem: IDropdownOption | undefined, row : number, column : IColumnConfig): void => {
|
||||
setGridEditState(true);
|
||||
|
||||
let activateCellEditTmp : any[] = [];
|
||||
activateCellEdit.forEach((item, index) => {
|
||||
if(row == index){
|
||||
item.properties[column.key].value = selectedDropdownItem?.text;
|
||||
}
|
||||
|
||||
activateCellEditTmp.push(item);
|
||||
});
|
||||
|
||||
setActivateCellEdit(activateCellEditTmp);
|
||||
};
|
||||
|
||||
const ChangeCellState = (key : string, rowNum : number, activateCurrentCell : boolean, activateCellEditArr : any[]) : any[] => {
|
||||
let activateCellEditTmp : any[] = [];
|
||||
activateCellEditTmp = [...activateCellEditArr];
|
||||
|
@ -1037,6 +1077,68 @@ const EditableGrid = (props: Props) => {
|
|||
/>
|
||||
}</span>
|
||||
break;
|
||||
case EditControlType.DropDown:
|
||||
return <span className={'row-' + rowNum! + '-col-' + index}>{
|
||||
(
|
||||
(!column.editable) || !(activateCellEdit && activateCellEdit[rowNum!] && activateCellEdit[rowNum!]['properties'][column.key] && activateCellEdit[rowNum!]['properties'][column.key].activated))
|
||||
?
|
||||
<span
|
||||
className={GetDynamicSpanStyles(column, item[column.key])}
|
||||
onClick={() => (props.enableCellEdit == true && column.editable == true && props.enableSingleClickCellEdit)
|
||||
?
|
||||
EditCellValue(column.key, rowNum!, true)
|
||||
:
|
||||
null}
|
||||
onDoubleClick={() => (props.enableCellEdit == true && column.editable == true && !props.enableSingleClickCellEdit)
|
||||
?
|
||||
EditCellValue(column.key, rowNum!, true)
|
||||
:
|
||||
null}
|
||||
>
|
||||
{item[column.key]}
|
||||
</span>
|
||||
:
|
||||
<Dropdown
|
||||
placeholder={column.dropdownValues?.filter(x => x.text == item[column.key])[0]?.text ?? 'Select an option'}
|
||||
options={column.dropdownValues ?? []}
|
||||
styles={dropdownStyles}
|
||||
onChange={(ev,selectedItem) => onDropDownChange(ev, selectedItem, rowNum!, column)}
|
||||
onDoubleClick={() => !activateCellEdit[rowNum!].isActivated ? onDropdownDoubleClickEvent(column.key, rowNum!, false) : null}
|
||||
/>
|
||||
}</span>
|
||||
break;
|
||||
case EditControlType.Picker:
|
||||
return <span>{
|
||||
((!column.editable) || !(activateCellEdit && activateCellEdit[rowNum!] && activateCellEdit[rowNum!]['properties'][column.key] && activateCellEdit[rowNum!]['properties'][column.key].activated))
|
||||
?
|
||||
<span
|
||||
className={GetDynamicSpanStyles(column, item[column.key])}
|
||||
onClick={() => (props.enableCellEdit == true && column.editable == true && props.enableSingleClickCellEdit)
|
||||
?
|
||||
EditCellValue(column.key, rowNum!, true)
|
||||
:
|
||||
null}
|
||||
onDoubleClick={() => (props.enableCellEdit == true && column.editable == true && !props.enableSingleClickCellEdit)
|
||||
?
|
||||
EditCellValue(column.key, rowNum!, true)
|
||||
:
|
||||
null}
|
||||
>
|
||||
{item[column.key]}
|
||||
</span>
|
||||
:
|
||||
<span onDoubleClick = {() => !activateCellEdit[rowNum!].isActivated ? onCellPickerDoubleClickEvent(column.key, rowNum!, false) : null}>
|
||||
<PickerControl
|
||||
selectedItemsLimit={column.pickerOptions?.tagsLimit}
|
||||
pickerTags={column.pickerOptions?.pickerTags ?? []}
|
||||
defaultTags={item[column.key] ? [item[column.key]] : []}
|
||||
minCharLimitForSuggestions={column.pickerOptions?.minCharLimitForSuggestions}
|
||||
onTaglistChanged={(selectedItem: ITag[] | undefined) => onCellPickerTagListChanged(selectedItem, rowNum!, column)}
|
||||
pickerDescriptionOptions={column.pickerOptions?.pickerDescriptionOptions}
|
||||
/>
|
||||
</span>
|
||||
}</span>
|
||||
break;
|
||||
default:
|
||||
return <span>{
|
||||
(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { getTheme, IDetailsColumnStyles, IStackStyles, IStackTokens, ITextFieldStyles, mergeStyleSets } from "office-ui-fabric-react";
|
||||
import { getTheme, IDetailsColumnStyles, IDropdownStyles, IStackStyles, IStackTokens, ITextFieldStyles, mergeStyleSets } from "office-ui-fabric-react";
|
||||
import { ICellStyleRulesType } from "../types/cellstyleruletype";
|
||||
import { IColumnConfig } from "../types/columnconfigtype";
|
||||
import { EvaluateRule } from "./helper";
|
||||
|
@ -48,6 +48,12 @@ export const controlClass = mergeStyleSets({
|
|||
},
|
||||
labelValue: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
pickerLabel: {
|
||||
color: '#323130',
|
||||
fontWeight:600,
|
||||
padding: '5px 0px',
|
||||
margin: '5px 0px'
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -80,4 +86,8 @@ export const horizontalGapStackTokens: IStackTokens = {
|
|||
padding: 10,
|
||||
};
|
||||
|
||||
export const textFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: {} };
|
||||
export const textFieldStyles: Partial<ITextFieldStyles> = { fieldGroup: {} };
|
||||
|
||||
export const dropdownStyles: Partial<IDropdownStyles> = {
|
||||
dropdown: { width: '90%' },
|
||||
};
|
|
@ -1,13 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import { DatePicker, IStackStyles, IStackTokens, ITextFieldStyles, mergeStyleSets, PrimaryButton, Stack, TextField } from "office-ui-fabric-react";
|
||||
import { DatePicker, divProperties, Dropdown, IDropdownOption, IStackStyles, IStackTokens, ITag, ITextFieldStyles, mergeStyleSets, PrimaryButton, Stack, TextField } from "office-ui-fabric-react";
|
||||
import React, { useState } from "react";
|
||||
import { IColumnConfig } from "../types/columnconfigtype";
|
||||
import { EditControlType } from "../types/editcontroltype";
|
||||
import { DayPickerStrings } from "./datepickerconfig";
|
||||
import { controlClass, horizontalGapStackTokens, stackStyles, textFieldStyles, verticalGapStackTokens } from "./editablegridstyles";
|
||||
import { IsValidDataType } from "./helper";
|
||||
import PickerControl from "./pickercontrol/picker";
|
||||
import SearchableDropdown from "./searchabledropdown/searchabledropdown";
|
||||
|
||||
interface Props {
|
||||
onChange: any;
|
||||
|
@ -17,6 +19,10 @@ interface Props {
|
|||
const EditPanel = (props: Props) => {
|
||||
const updateObj : any = {};
|
||||
|
||||
const onDropDownChange = (event: React.FormEvent<HTMLDivElement>, selectedDropdownItem: IDropdownOption | undefined, item : any): void => {
|
||||
updateObj[item.key] = selectedDropdownItem?.text;
|
||||
}
|
||||
|
||||
const onTextUpdate = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text: string, column : IColumnConfig): void => {
|
||||
debugger;
|
||||
if(!IsValidDataType(column.dataType, text) || text.trim() == ''){
|
||||
|
@ -35,6 +41,15 @@ const EditPanel = (props: Props) => {
|
|||
updateObj[item.key] = date;
|
||||
};
|
||||
|
||||
const onCellPickerTagListChanged = (cellPickerTagList: ITag[] | undefined, item : any) : void => {
|
||||
if(cellPickerTagList && cellPickerTagList[0] && cellPickerTagList[0].name){
|
||||
updateObj[item.key] = cellPickerTagList[0].name;
|
||||
}
|
||||
else{
|
||||
updateObj[item.key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
const createTextFields = () : any[] => {
|
||||
let tmpRenderObj : any[] = [];
|
||||
props.columnConfigurationData.forEach((item, index) => {
|
||||
|
@ -51,6 +66,26 @@ const EditPanel = (props: Props) => {
|
|||
value={new Date()}
|
||||
/>);
|
||||
break;
|
||||
case EditControlType.Picker:
|
||||
tmpRenderObj.push(<div>
|
||||
<span className={controlClass.pickerLabel}>{item.text}</span>
|
||||
<PickerControl
|
||||
selectedItemsLimit={1}
|
||||
pickerTags={item.pickerOptions?.pickerTags ?? []}
|
||||
minCharLimitForSuggestions={2}
|
||||
onTaglistChanged={(selectedItem: ITag[] | undefined) => onCellPickerTagListChanged(selectedItem, item)}
|
||||
pickerDescriptionOptions={item.pickerOptions?.pickerDescriptionOptions}
|
||||
/></div>);
|
||||
break;
|
||||
case EditControlType.DropDown:
|
||||
tmpRenderObj.push(
|
||||
<Dropdown
|
||||
label={item.text}
|
||||
options={item.dropdownValues ?? []}
|
||||
onChange={(ev, selected) => onDropDownChange(ev, selected, item)}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
tmpRenderObj.push(<TextField
|
||||
name={item.text}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { mergeStyleSets } from "office-ui-fabric-react";
|
||||
|
||||
export const classNames = mergeStyleSets({
|
||||
plainCard: {
|
||||
width: 200,
|
||||
height: 140,
|
||||
display: 'flex',
|
||||
padding: '10px',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
|
@ -0,0 +1,128 @@
|
|||
import { HoverCard, HoverCardType, IBasePickerSuggestionsProps, IInputProps, IPlainCardProps, ISuggestionItemProps, ITag, TagPicker } from "office-ui-fabric-react"
|
||||
import React, { MouseEventHandler } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { IPickerDescriptionOption, IPickerTagDescription } from "../../types/columnconfigtype";
|
||||
import { classNames } from "./picker.styles";
|
||||
|
||||
interface Props {
|
||||
selectedItemsLimit? : number;
|
||||
pickerTags : string[];
|
||||
defaultTags?: string[];
|
||||
minCharLimitForSuggestions?: number;
|
||||
onTaglistChanged?: any;
|
||||
pickerDescriptionOptions?: IPickerDescriptionOption;
|
||||
}
|
||||
|
||||
const PickerControl = (props: Props) => {
|
||||
|
||||
const [pickerTags, setPickerTags] = React.useState<ITag[]>([]);
|
||||
const [defaultTags, setdefaultTags] = React.useState<ITag[]>([]);
|
||||
const [pickerDescriptions, setPickerDescriptions] = React.useState<IPickerTagDescription[]>([]);
|
||||
const [pickerFilteredText, setPickerFilteredText] = React.useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
if(props.pickerTags && props.pickerTags.length > 0){
|
||||
setPickerTags(props.pickerTags.map(item => ({ key: item, name: item })));
|
||||
setdefaultTags(props?.defaultTags?.map(item => ({ key: item, name: item })) ?? []);
|
||||
}
|
||||
}, [props.pickerTags]);
|
||||
|
||||
useEffect(() => {
|
||||
if(props && props.pickerDescriptionOptions && props.pickerDescriptionOptions.enabled && props.pickerDescriptionOptions.values){
|
||||
setPickerDescriptions(props.pickerDescriptionOptions.values);
|
||||
}
|
||||
}, [props.pickerDescriptionOptions]);
|
||||
|
||||
const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
|
||||
suggestionsHeaderText: !props.minCharLimitForSuggestions ? 'Suggested tags' : (pickerFilteredText.length >= props.minCharLimitForSuggestions ? 'Suggested tags' : ''),
|
||||
noResultsFoundText: !props.minCharLimitForSuggestions ? 'No suggested tags found' : (pickerFilteredText.length >= props.minCharLimitForSuggestions ? 'No suggested tags found' : ''),
|
||||
};
|
||||
|
||||
const getTextFromItem = (item: ITag) => item.name;
|
||||
|
||||
const listContainsTagList = (tag: ITag, tagList?: ITag[]) => {
|
||||
if (!tagList || !tagList.length || tagList.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return tagList.some(compareTag => compareTag.key === tag.key);
|
||||
};
|
||||
|
||||
const filterSuggestedTags = (filterText: string, tagList: ITag[] | undefined): ITag[] => {
|
||||
setPickerFilteredText(filterText);
|
||||
|
||||
if(!props.minCharLimitForSuggestions){
|
||||
return filterText
|
||||
? pickerTags.filter(
|
||||
tag => tag.name.toLowerCase().indexOf(filterText.toLowerCase()) === 0 && !listContainsTagList(tag, tagList),
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
if(filterText.length >= props.minCharLimitForSuggestions){
|
||||
return filterText
|
||||
? pickerTags.filter(
|
||||
tag => tag.name.toLowerCase().indexOf(filterText.toLowerCase()) === 0 && !listContainsTagList(tag, tagList),
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
};
|
||||
|
||||
const inputProps: IInputProps = {
|
||||
'aria-label': 'Tag picker'
|
||||
};
|
||||
|
||||
const onFilterTagListChanged = React.useCallback((tagList: ITag[] | undefined): void => {
|
||||
setdefaultTags(tagList!);
|
||||
if(props.onTaglistChanged){
|
||||
props.onTaglistChanged(tagList);
|
||||
}
|
||||
},[]);
|
||||
|
||||
const onRenderPlainCard = (item : ITag): JSX.Element => {
|
||||
return (
|
||||
<div className={classNames.plainCard}>
|
||||
{pickerDescriptions.filter(x => x.key == item.key)[0].description}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const onRenderSuggestionsItem = (tag: ITag, itemProps: ISuggestionItemProps<ITag>) : JSX.Element => {
|
||||
const plainCardProps: IPlainCardProps = {
|
||||
onRenderPlainCard: onRenderPlainCard,
|
||||
renderData: tag
|
||||
};
|
||||
|
||||
if(pickerDescriptions && pickerDescriptions.length > 0){
|
||||
return (<HoverCard
|
||||
type={HoverCardType.plain}
|
||||
plainCardProps={plainCardProps}
|
||||
instantOpenOnClick
|
||||
>
|
||||
<div style={{ padding:'10px' }} key={tag.key}>{tag.name}</div>
|
||||
</HoverCard>);
|
||||
}
|
||||
|
||||
return <div style={{ padding:'10px' }} key={tag.key}>{tag.name}</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TagPicker
|
||||
removeButtonAriaLabel="Remove"
|
||||
onResolveSuggestions={filterSuggestedTags}
|
||||
getTextFromItem={getTextFromItem}
|
||||
pickerSuggestionsProps={pickerSuggestionsProps}
|
||||
itemLimit={props.selectedItemsLimit ?? 1}
|
||||
onChange={onFilterTagListChanged}
|
||||
selectedItems={defaultTags}
|
||||
inputProps={inputProps}
|
||||
onRenderSuggestionsItem={onRenderSuggestionsItem}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default PickerControl
|
|
@ -0,0 +1,74 @@
|
|||
import { IDropdownProps } from "@fluentui/react";
|
||||
import { Callout, DirectionalHint, Dropdown, DropdownMenuItemType, IDropdownOption, mergeStyles, ScrollablePane, ScrollbarVisibility, Stack, TextField } from "office-ui-fabric-react";
|
||||
import { dropdownStyles, stackTokens, styles } from "./searchabledropdownstyles";
|
||||
import { useId } from '@uifabric/react-hooks';
|
||||
import { useEffect } from "react";
|
||||
import React from "react";
|
||||
|
||||
interface Props extends IDropdownProps {
|
||||
field?: string;
|
||||
minCharLengthBeforeSuggestion?: number;
|
||||
}
|
||||
|
||||
const SearchableDropdown = (props: Props) => {
|
||||
|
||||
const [dropdownOptions, setDropdownOptions] = React.useState<IDropdownOption[]>([]);
|
||||
const [placeholder, setPlaceHolder] = React.useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
setDropdownOptions(props.options);
|
||||
setPlaceHolder(props.placeholder);
|
||||
}, [props.options]);
|
||||
|
||||
const onFilterTextUpdate = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, searchText: string | undefined): void => {
|
||||
debugger;
|
||||
var dropdownOptionsTmp : IDropdownOption[] = [...props.options.filter(x => x.itemType != DropdownMenuItemType.Header)];
|
||||
console.log('filtered');
|
||||
console.log(dropdownOptionsTmp.filter(x => x.text.toLowerCase().indexOf(searchText?.toLowerCase() ?? '') > -1));
|
||||
var matches : IDropdownOption[] = dropdownOptionsTmp.filter(x => x.text.toLowerCase().indexOf(searchText?.toLowerCase() ?? '') > -1);
|
||||
setPlaceHolder(`[${matches.length.toString()} match${matches.length != 1 ? 'es' : ''} found]`);
|
||||
setDropdownOptions(matches);
|
||||
}
|
||||
|
||||
const labelId: string = useId('dropdown-callout-label');
|
||||
const descriptionId: string = useId('dropdown-callout-description');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Callout
|
||||
className={styles.callout}
|
||||
ariaLabelledBy={labelId}
|
||||
ariaDescribedBy={descriptionId}
|
||||
role="filtercallout"
|
||||
gapSpace={10}
|
||||
target={`.${props.className}`}
|
||||
isBeakVisible={true}
|
||||
directionalHint={DirectionalHint.bottomCenter}
|
||||
>
|
||||
<Stack verticalAlign="start" tokens={stackTokens}>
|
||||
<TextField
|
||||
id={`id-${props.className}`}
|
||||
className={styles.textFieldClass}
|
||||
placeholder={`Search ${props.field ?? ''}`}
|
||||
onChange={(ev, text) => onFilterTextUpdate(ev, text)}
|
||||
/>
|
||||
<div className={mergeStyles({ height: '10vh', width: '30vh', position: 'relative', backgroundColor: 'white' })}>
|
||||
<ScrollablePane scrollbarVisibility={ScrollbarVisibility.auto}>
|
||||
<Dropdown
|
||||
label={props.label}
|
||||
placeholder={placeholder}
|
||||
options={dropdownOptions ?? []}
|
||||
styles={props.styles}
|
||||
onChange={props.onChange}
|
||||
onDoubleClick={props.onDoubleClick}
|
||||
/>
|
||||
</ScrollablePane>
|
||||
</div>
|
||||
</Stack>
|
||||
</Callout>
|
||||
</>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default SearchableDropdown;
|
|
@ -0,0 +1,22 @@
|
|||
import { IDropdownStyles, IStackTokens, mergeStyleSets } from "office-ui-fabric-react";
|
||||
|
||||
export const dropdownStyles: Partial<IDropdownStyles> = {
|
||||
dropdown: { width: '90%', margin:10 },
|
||||
};
|
||||
|
||||
export const styles = mergeStyleSets({
|
||||
callout: {
|
||||
maxWidth: 500,
|
||||
padding: 30
|
||||
},
|
||||
textFieldClass:{
|
||||
display: 'block',
|
||||
marginTop: 10,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
width: '90%',
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export const stackTokens: IStackTokens = { childrenGap: 20, maxWidth:1000 };
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import { ConstrainMode, IColumn, IDetailsHeaderProps } from 'office-ui-fabric-react/lib/components/DetailsList/DetailsList.types';
|
||||
import { IDropdownOption } from "office-ui-fabric-react";
|
||||
import { CalculationType } from "./calculationtype";
|
||||
import { ICellStyleRulesType } from './cellstyleruletype';
|
||||
import { EditControlType } from "./editcontroltype";
|
||||
|
@ -20,4 +21,23 @@ export interface IColumnConfig extends IColumn {
|
|||
maxLength?: number;
|
||||
applyColumnFilter?: boolean;
|
||||
cellStyleRule?: ICellStyleRulesType;
|
||||
};
|
||||
dropdownValues?: IDropdownOption[];
|
||||
pickerOptions?: IPickerOptions;
|
||||
};
|
||||
|
||||
export interface IPickerOptions {
|
||||
tagsLimit?: number;
|
||||
minCharLimitForSuggestions?: number;
|
||||
pickerTags: string[];
|
||||
pickerDescriptionOptions?: IPickerDescriptionOption;
|
||||
}
|
||||
|
||||
export interface IPickerDescriptionOption {
|
||||
enabled: boolean;
|
||||
values: IPickerTagDescription[];
|
||||
}
|
||||
|
||||
export interface IPickerTagDescription {
|
||||
key: string;
|
||||
description: string;
|
||||
}
|
|
@ -7,5 +7,6 @@ export enum EditControlType {
|
|||
DropDown,
|
||||
Date,
|
||||
MultilineTextField,
|
||||
DateTime
|
||||
DateTime,
|
||||
Picker
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noEmit": false,
|
||||
"jsx": "react-jsx",
|
||||
"downlevelIteration": true,
|
||||
"declaration": true,
|
||||
|
|
Загрузка…
Ссылка в новой задаче